@sema-agent/core 7.44.0 → 7.46.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 +418 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +54 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +41 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -1,12 +1,136 @@
|
|
|
1
|
+
import { parsePermissionRule } from "../core/permission-rule-syntax.js";
|
|
2
|
+
import { HAND_TOOLS } from "../core/tool-catalog-entries.js";
|
|
3
|
+
export function readDeniedEntry(entry) {
|
|
4
|
+
if (typeof entry !== "string")
|
|
5
|
+
return { kind: "not_a_string", type: valueTypeLabel(entry) };
|
|
6
|
+
if (entry !== entry.trim())
|
|
7
|
+
return { kind: "name", name: entry, content: false };
|
|
8
|
+
const parsed = parsePermissionRule(entry);
|
|
9
|
+
return { kind: "name", name: parsed.toolName, content: parsed.ruleContent !== undefined };
|
|
10
|
+
}
|
|
11
|
+
export function valueTypeLabel(value) {
|
|
12
|
+
if (value === null)
|
|
13
|
+
return "null";
|
|
14
|
+
switch (typeof value) {
|
|
15
|
+
case "string":
|
|
16
|
+
return "a string";
|
|
17
|
+
case "undefined":
|
|
18
|
+
return "undefined";
|
|
19
|
+
case "number":
|
|
20
|
+
return "a number";
|
|
21
|
+
case "boolean":
|
|
22
|
+
return "a boolean";
|
|
23
|
+
case "bigint":
|
|
24
|
+
return "a bigint";
|
|
25
|
+
case "symbol":
|
|
26
|
+
return "a symbol";
|
|
27
|
+
case "function":
|
|
28
|
+
return "a function";
|
|
29
|
+
default:
|
|
30
|
+
try {
|
|
31
|
+
return Array.isArray(value) ? "an array" : "an object";
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return "an object";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const isList = (value) => Array.isArray(value);
|
|
39
|
+
const UNREADABLE_LIST = Object.freeze({});
|
|
40
|
+
export function readToolNameList(value) {
|
|
41
|
+
if (value === UNREADABLE_LIST)
|
|
42
|
+
return { kind: "unreadable" };
|
|
43
|
+
let list;
|
|
44
|
+
try {
|
|
45
|
+
list = isList(value) ? value : undefined;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { kind: "not_a_list", type: "an object" };
|
|
49
|
+
}
|
|
50
|
+
if (list === undefined)
|
|
51
|
+
return { kind: "not_a_list", type: valueTypeLabel(value) };
|
|
52
|
+
try {
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (let i = 0; i < list.length; i++)
|
|
55
|
+
entries.push(list[i]);
|
|
56
|
+
return { kind: "entries", entries };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { kind: "unreadable" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function snapshotToolNameList(value) {
|
|
63
|
+
const read = readToolNameList(value);
|
|
64
|
+
return read.kind === "entries" ? read.entries : read.kind === "unreadable" ? UNREADABLE_LIST : value;
|
|
65
|
+
}
|
|
66
|
+
const LIST_NOUN = { allowed: "allowed tools", denied: "denied tools" };
|
|
67
|
+
const LIST_NAME = { allowed: "an allowed-tools list", denied: "a denied-tools list" };
|
|
68
|
+
export function notAStringSentence(where, side, index, type) {
|
|
69
|
+
return `${where} declares in its ${LIST_NOUN[side]}, as entry ${index + 1}, a value that is ${type}, not a tool name — ${LIST_NAME[side]} holds tool names, each a string. Fix the entry.`;
|
|
70
|
+
}
|
|
71
|
+
export function notAListSentence(where, side, list) {
|
|
72
|
+
return list.kind === "not_a_list"
|
|
73
|
+
? `${where} declares in its ${LIST_NOUN[side]} a value that is ${list.type}, not a list of tool names. Fix the list.`
|
|
74
|
+
: `${where} declares in its ${LIST_NOUN[side]} a list whose entries cannot be read (an accessor or a proxy trap threw). Fix the list.`;
|
|
75
|
+
}
|
|
76
|
+
export function toolNameListFlaw(where, side, value) {
|
|
77
|
+
if (value === undefined || value === null)
|
|
78
|
+
return undefined;
|
|
79
|
+
const list = readToolNameList(value);
|
|
80
|
+
if (list.kind !== "entries")
|
|
81
|
+
return notAListSentence(where, side, list);
|
|
82
|
+
for (let i = 0; i < list.entries.length; i++) {
|
|
83
|
+
const entry = list.entries[i];
|
|
84
|
+
if (typeof entry !== "string")
|
|
85
|
+
return notAStringSentence(where, side, i, valueTypeLabel(entry));
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
export function snapshotDefinitionLists(def) {
|
|
90
|
+
const copy = { ...def };
|
|
91
|
+
if (copy.allowTools !== undefined)
|
|
92
|
+
copy.allowTools = snapshotToolNameList(copy.allowTools);
|
|
93
|
+
if (copy.denyTools !== undefined)
|
|
94
|
+
copy.denyTools = snapshotToolNameList(copy.denyTools);
|
|
95
|
+
return copy;
|
|
96
|
+
}
|
|
97
|
+
export function allowedToolNames(allowTools) {
|
|
98
|
+
if (allowTools === undefined || allowTools === null)
|
|
99
|
+
return undefined;
|
|
100
|
+
const list = readToolNameList(allowTools);
|
|
101
|
+
if (list.kind !== "entries")
|
|
102
|
+
return new Set();
|
|
103
|
+
if (list.entries.includes("*"))
|
|
104
|
+
return undefined;
|
|
105
|
+
const names = new Set();
|
|
106
|
+
for (const entry of list.entries)
|
|
107
|
+
if (typeof entry === "string")
|
|
108
|
+
names.add(entry);
|
|
109
|
+
return names;
|
|
110
|
+
}
|
|
111
|
+
function deniedNames(denyTools) {
|
|
112
|
+
const names = new Set();
|
|
113
|
+
const list = readToolNameList(denyTools);
|
|
114
|
+
if (list.kind !== "entries")
|
|
115
|
+
return names;
|
|
116
|
+
for (const entry of list.entries) {
|
|
117
|
+
const read = readDeniedEntry(entry);
|
|
118
|
+
if (read.kind === "name")
|
|
119
|
+
names.add(read.name);
|
|
120
|
+
}
|
|
121
|
+
return names;
|
|
122
|
+
}
|
|
1
123
|
export function toolNameAllowed(name, allowTools, denyTools) {
|
|
2
|
-
const allow =
|
|
3
|
-
const
|
|
124
|
+
const allow = allowedToolNames(allowTools);
|
|
125
|
+
const denied = deniedNames(denyTools);
|
|
126
|
+
const deny = denied.size > 0 ? denied : undefined;
|
|
4
127
|
const n = name;
|
|
5
128
|
return (!allow || allow.has(n)) && (!deny || !deny.has(n));
|
|
6
129
|
}
|
|
7
130
|
export function resolveToolSubset(pool, allowTools, denyTools) {
|
|
8
|
-
const allow =
|
|
9
|
-
const
|
|
131
|
+
const allow = allowedToolNames(allowTools);
|
|
132
|
+
const denied = deniedNames(denyTools);
|
|
133
|
+
const deny = denied.size > 0 ? denied : undefined;
|
|
10
134
|
if (!allow && !deny)
|
|
11
135
|
return [...pool];
|
|
12
136
|
return pool.filter((t) => {
|
|
@@ -14,3 +138,9 @@ export function resolveToolSubset(pool, allowTools, denyTools) {
|
|
|
14
138
|
return (!allow || matches(allow)) && (!deny || !matches(deny));
|
|
15
139
|
});
|
|
16
140
|
}
|
|
141
|
+
export function resolveAgentToolSubset(pool, allowTools, denyTools) {
|
|
142
|
+
const kept = new Set(resolveToolSubset([...pool, ...HAND_TOOLS], allowTools, denyTools));
|
|
143
|
+
const handed = pool.filter((t) => kept.has(t));
|
|
144
|
+
const mountedUnder = new Set(handed.map((t) => t.name));
|
|
145
|
+
return { handed, withheldHands: HAND_TOOLS.filter((t) => !kept.has(t) && !mountedUnder.has(t.name)).map((t) => t.name) };
|
|
146
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The configuration directory's name below a home: the one segment the deny tables spell the agent's own settings under. */
|
|
2
|
+
export declare const AGENT_CONFIG_DIR_SEGMENT = ".sema";
|
|
3
|
+
/** The code a declared root that names no directory is refused with, wherever it is declared. */
|
|
4
|
+
export declare const AGENT_CONFIG_DIR_INVALID_CODE = "config.agent_config_dir_invalid";
|
|
5
|
+
/** A run (the `/`-separated segments of a deny row, home word already removed) read below the configuration directory:
|
|
6
|
+
* `.sema/settings.json` ⇒ `settings.json`, `.sema` ⇒ `""`, and `undefined` for a run that does not open with the
|
|
7
|
+
* directory's name. The first segment is compared as the row compares it (ASCII-case-insensitive unless the row opts out).
|
|
8
|
+
*
|
|
9
|
+
* @contract agent_config_dir.one_reading — which rows a declared configuration root relocates is decided HERE and nowhere
|
|
10
|
+
* else: a row that opens with the `.sema` segment names its tail below the root, in the read placement, in the always-on
|
|
11
|
+
* write-protection table and in the opt-in deny set alike; the root adds a place and never moves the default one, a row that
|
|
12
|
+
* does not open with that segment keeps its place, and a face with a list of its own of "the rows that relocate" is a fork
|
|
13
|
+
* of this sentence, not a variant of it. */
|
|
14
|
+
export declare function agentConfigDirTail(run: string, caseSensitive?: boolean): string | undefined;
|
|
15
|
+
/** The segments of a declared root in lexical normal form — what a path's leading segments are compared with. */
|
|
16
|
+
export declare function agentConfigDirSegments(dir: string): string[];
|
|
17
|
+
/** The declared configuration root as given (`undefined` = none declared, the default location), or a refusal naming the
|
|
18
|
+
* seat and the shape of the value: not a string, empty, not absolute, no such directory, not a directory, not readable.
|
|
19
|
+
* The directory is looked at on the host's own file system — the host that declares the root is the host that keeps it. */
|
|
20
|
+
export declare function checkedAgentConfigDir(value: string | undefined, seat: string): string | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { isAbsolutePathForm, normalizeAbsPathLexically } from "../tools/fs/safety.js";
|
|
3
|
+
export const AGENT_CONFIG_DIR_SEGMENT = ".sema";
|
|
4
|
+
export const AGENT_CONFIG_DIR_INVALID_CODE = "config.agent_config_dir_invalid";
|
|
5
|
+
export function agentConfigDirTail(run, caseSensitive = false) {
|
|
6
|
+
const [first, ...rest] = run.split("/").filter((segment) => segment.length > 0);
|
|
7
|
+
if (first === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const folded = caseSensitive ? first : first.replace(/[A-Z]/g, (c) => c.toLowerCase());
|
|
10
|
+
return folded === AGENT_CONFIG_DIR_SEGMENT ? rest.join("/") : undefined;
|
|
11
|
+
}
|
|
12
|
+
export function agentConfigDirSegments(dir) {
|
|
13
|
+
return normalizeAbsPathLexically(dir).split(/[\\/]/).filter((segment) => segment.length > 0);
|
|
14
|
+
}
|
|
15
|
+
function shown(value) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value) ?? String(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return typeof value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function invalidAgentConfigDir(seat, value, why) {
|
|
24
|
+
const e = new Error(`${seat} ${shown(value)} ${why} — it must be the absolute path of an existing directory (the host's own configuration root); ` +
|
|
25
|
+
`a value that names no directory would guard nothing, so it is refused rather than read as the default \`~/${AGENT_CONFIG_DIR_SEGMENT}\` location.`);
|
|
26
|
+
e.code = AGENT_CONFIG_DIR_INVALID_CODE;
|
|
27
|
+
return e;
|
|
28
|
+
}
|
|
29
|
+
export function checkedAgentConfigDir(value, seat) {
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (typeof value !== "string")
|
|
33
|
+
throw invalidAgentConfigDir(seat, value, `is a ${value === null ? "null" : typeof value}, not a string`);
|
|
34
|
+
if (value.length === 0)
|
|
35
|
+
throw invalidAgentConfigDir(seat, value, "is an empty string");
|
|
36
|
+
if (!isAbsolutePathForm(value))
|
|
37
|
+
throw invalidAgentConfigDir(seat, value, "is not an absolute path");
|
|
38
|
+
let isDirectory;
|
|
39
|
+
try {
|
|
40
|
+
isDirectory = statSync(value).isDirectory();
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
const code = typeof e === "object" && e !== null && "code" in e && typeof e.code === "string" ? e.code : "unreadable";
|
|
44
|
+
throw invalidAgentConfigDir(seat, value, code === "ENOENT" || code === "ENOTDIR" ? "names no existing directory" : `could not be examined (${code})`);
|
|
45
|
+
}
|
|
46
|
+
if (!isDirectory)
|
|
47
|
+
throw invalidAgentConfigDir(seat, value, "names a file, not a directory");
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
@@ -33,7 +33,7 @@ export interface AgentDefinition {
|
|
|
33
33
|
* the delegation tool's name cannot re-delegate (CC Explore/Plan `disallowedTools` include Agent,
|
|
34
34
|
* pretty.js:409189/486948). */
|
|
35
35
|
allowTools?: string[];
|
|
36
|
-
/** Tool denylist (a deny always wins)
|
|
36
|
+
/** Tool denylist (a deny always wins), tool NAMES: `Tool(content)` hides the whole `Tool`. See {@link allowTools} for the per-agent delegation-tool note. */
|
|
37
37
|
denyTools?: string[];
|
|
38
38
|
/**
|
|
39
39
|
* F7 (CC 2.1.198 parity, per-agent `skills` — 锚 pretty.js:422126 `e.skills ?? []`, GA/ungated in 198:
|
|
@@ -116,7 +116,7 @@ export interface SkillSpec {
|
|
|
116
116
|
* either list asks once (`Execute skill: <name>`, the card listing what it releases); a skill the person invoked by
|
|
117
117
|
* name ({@link import("./task-spec.js").TaskSpec.userInvokedSkills}) grants without asking. The grant holds for that
|
|
118
118
|
* run (a steer it takes in does not end it) and the next runTask starts without it. An entry this grammar reads as nothing — `*` (it voids an allow list; on a deny list it is inert and the rest of the
|
|
119
|
-
* list still refuses), `mcp__*`, a write path, a domain — grants nothing and is announced at the start of the run (`permission.allowed_tool_not_granted`).
|
|
119
|
+
* list still refuses), `mcp__*`, a write path, a domain — grants nothing and is announced at the start of the run (`permission.allowed_tool_not_granted`). A table whose value is not a list (a string, an object…) reads as one such entry; `null` is no table.
|
|
120
120
|
*/
|
|
121
121
|
allowedTools?: string[];
|
|
122
122
|
/** The tools refused for the rest of the run this skill is activated in — the frontmatter `disallowed-tools` list,
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import { type RuleToolNameVerdict } from "./tool-name-aliases.js";
|
|
2
2
|
import { type ToolCallRequest } from "./tool-policy.js";
|
|
3
3
|
import { type AllowRuleMember, type AllowRuleShadow } from "./allow-rule-shadow.js";
|
|
4
|
-
import { type PersistedRule } from "./permission-rule-model.js";
|
|
4
|
+
import { type PathRuleBases, type PersistedRule } from "./permission-rule-model.js";
|
|
5
5
|
import { type AskClass, type AssertAllKeysHandled, type PermissionMode, type PersistedRuleMandate } from "./ask-origin.js";
|
|
6
|
+
import { type ToolNameListReading } from "../agents/tool-filter.js";
|
|
6
7
|
import { type EngineNotice } from "./engine-notice.js";
|
|
7
8
|
/** Why an entry grants (or refuses) nothing — the closed set the start-of-run notice names, for every list. `tool_name`: the
|
|
8
9
|
* entry's tool name is one the name judgment reads as stale against the run's roster (`verdict` says which); `near_miss`: the
|
|
9
10
|
* run mounts no tool of that exact name but mounts one that differs only by case (`currentName` is its spelling). */
|
|
10
11
|
export declare const ALLOW_ENTRY_INERT_REASONS: readonly ["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar", "tool_name", "near_miss"];
|
|
11
12
|
export type AllowEntryInertReason = (typeof ALLOW_ENTRY_INERT_REASONS)[number];
|
|
12
|
-
/** The facts beside a `tool_name` / `near_miss`
|
|
13
|
+
/** The facts beside a reason: for `tool_name` / `near_miss` the judgment's word and the spelling to write instead when there is
|
|
14
|
+
* one; for a list member that is not a string (`grammar`) the TYPE of the value (`a number`, `null`, `an object`…) — `entry` then
|
|
15
|
+
* holds {@link nonStringMemberLabel}, never the value. */
|
|
13
16
|
export interface AllowEntryInertDetail {
|
|
14
17
|
readonly verdict?: Exclude<RuleToolNameVerdict["kind"], "current">;
|
|
15
18
|
readonly currentName?: string;
|
|
19
|
+
readonly valueType?: string;
|
|
16
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* How a list member that is not a string is named wherever a list's entries are named (the inert record's `entry`, the Skill
|
|
23
|
+
* card's binding): its TYPE, in a spelling no readable entry can have — a readable entry's name holds no whitespace and a
|
|
24
|
+
* name-only entry holds no parenthesis, and this label holds a space and none. It can equal only another entry that reads as
|
|
25
|
+
* nothing, so a binding that holds it never stands for an entry that grants or refuses.
|
|
26
|
+
*/
|
|
27
|
+
export declare function nonStringMemberLabel(value: unknown): string;
|
|
28
|
+
/** How a tool-name list's VALUE that is not a list stands as one entry (the same kind of spelling as {@link nonStringMemberLabel}: it
|
|
29
|
+
* reads as `grammar`): its TYPE, or that its entries could not be read — never the value. */
|
|
30
|
+
export declare function nonListValueLabel(read: Exclude<ToolNameListReading, {
|
|
31
|
+
kind: "entries";
|
|
32
|
+
}>): string;
|
|
17
33
|
/** One entry a list reads as nothing. */
|
|
18
34
|
export type AllowEntryInert = {
|
|
19
35
|
readonly entry: string;
|
|
@@ -78,12 +94,14 @@ export interface AllowEntryRow {
|
|
|
78
94
|
readonly entry: string;
|
|
79
95
|
readonly compiled: CompiledAllowEntry;
|
|
80
96
|
}
|
|
81
|
-
/** The
|
|
97
|
+
/** The facts a list's content row is judged under: the leg's root and session (which rows apply), the live working
|
|
98
|
+
* directory (where a command's `cd` lands), and the ANCHORS of the list's path rows — the leg the list was installed
|
|
99
|
+
* on (its root and environment home): a path row of a list never moves with a `cd`. */
|
|
82
100
|
export interface AllowEntryJudgeContext {
|
|
83
101
|
readonly root: string | undefined;
|
|
84
102
|
readonly sessionId: string | undefined;
|
|
85
103
|
readonly liveCwd: () => string | undefined;
|
|
86
|
-
readonly
|
|
104
|
+
readonly anchors: PathRuleBases;
|
|
87
105
|
}
|
|
88
106
|
/**
|
|
89
107
|
* The ONE list matcher, both directions — the rows that speak for the call, or undefined. ALLOW: the first name row that
|
|
@@ -189,6 +207,7 @@ declare const SHADOW_REASON: {
|
|
|
189
207
|
readonly content_question: "the call is the engine's own question to a person";
|
|
190
208
|
readonly hook: "a PreToolUse hook asks about this call — the deployment's own screening is answered by a person";
|
|
191
209
|
readonly ancestor: "the task that delegated this one owns this question";
|
|
210
|
+
readonly dangerous_delete: "this removal targets a protected location (the filesystem root, a top-level directory, a home directory, or a workspace directory or one of its ancestors) — it is cleared by confirming this call, never by an allow rule";
|
|
192
211
|
readonly unverifiable_delete: "this removal could not be verified safe — it is cleared by confirming this call, never by an allow rule";
|
|
193
212
|
readonly transcript_write: "this write targets the session transcript — it is cleared by confirming this call, never by an allow rule";
|
|
194
213
|
readonly org_rule: "an organization policy rule requires approval for this call";
|
|
@@ -244,6 +263,13 @@ export type AllowEntryInertNote = ({
|
|
|
244
263
|
} & AllowEntryInert) | ({
|
|
245
264
|
readonly source: AllowEntrySource;
|
|
246
265
|
} & AllowEntryInert);
|
|
266
|
+
/**
|
|
267
|
+
* THE ONE WRITER of a list entry into a sentence (the start-of-run notice, a skill's refusal and its decisions, a sub-agent
|
|
268
|
+
* list's refusals and advisories): the entry as a JSON string — quotes, backslashes and the usual escapes — with every
|
|
269
|
+
* character that prints as nothing, or as a space, written as `\uXXXX` (`\u{…}` beyond the BMP) and a run of spaces kept
|
|
270
|
+
* space by space, so what a person reads is what the engine read. Capped on what is printed.
|
|
271
|
+
*/
|
|
272
|
+
export declare function renderListEntry(entry: string): string;
|
|
247
273
|
/**
|
|
248
274
|
* One entry that reads as nothing, as a person reads it: the entry as written (quoted, so a surrounding space shows) and why
|
|
249
275
|
* — the reason word, with the spelling to write when the reading has one. The one phrasing the start-of-run notice and a
|
package/dist/core/allow-entry.js
CHANGED
|
@@ -6,15 +6,23 @@ import { COARSE_SHELL_TOOLS } from "./tool-policy.js";
|
|
|
6
6
|
import { ALLOW_RULE_MEMBERS } from "./allow-rule-shadow.js";
|
|
7
7
|
import { COMMAND_RULE_TOOL, READ_RULE_TOOL, findAdmittingRule, laneToolNamesOf, parseRuleText, programRunReachOf, ruleToolGrammarOf } from "./permission-rule-model.js";
|
|
8
8
|
import { pathRuleLaneAnswer } from "./runner/permission-rule-lanes.js";
|
|
9
|
+
import { callTargetReadingOf } from "./effective-path-target.js";
|
|
9
10
|
import { splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
10
11
|
import { mandateDisclosureOf } from "./ask-origin.js";
|
|
11
12
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
13
|
+
import { valueTypeLabel } from "../agents/tool-filter.js";
|
|
12
14
|
import { deliverEngineNotice } from "./engine-notice.js";
|
|
13
15
|
export const ALLOW_ENTRY_INERT_REASONS = Object.freeze(["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar", "tool_name", "near_miss"]);
|
|
16
|
+
export function nonStringMemberLabel(value) {
|
|
17
|
+
return `<not a string: ${valueTypeLabel(value)}>`;
|
|
18
|
+
}
|
|
19
|
+
export function nonListValueLabel(read) {
|
|
20
|
+
return read.kind === "not_a_list" ? `<not a list: ${read.type}>` : "<a list whose entries cannot be read>";
|
|
21
|
+
}
|
|
14
22
|
function ruleRowOf(parsed) {
|
|
15
23
|
return { ...parsed, scope: { kind: "global" }, adds: [{ dot: { actor: "list", counter: 1 }, origin: "user", createdAt: "" }] };
|
|
16
24
|
}
|
|
17
|
-
const TOOL_NAME_SPELLING = /^[^\s()]
|
|
25
|
+
const TOOL_NAME_SPELLING = /^[^\s()\p{Cc}\p{Cf}]+$/u;
|
|
18
26
|
function mountedSpellingOf(name, roster) {
|
|
19
27
|
const folded = name.toLowerCase();
|
|
20
28
|
for (const mounted of roster)
|
|
@@ -36,6 +44,8 @@ function toolNameReading(name, roster) {
|
|
|
36
44
|
function readAllowEntry(raw, behavior, roster) {
|
|
37
45
|
const inert = (reason) => ({ inert: reason });
|
|
38
46
|
const row = (compiled) => ({ compiled });
|
|
47
|
+
if (typeof raw !== "string")
|
|
48
|
+
return { inert: "grammar", detail: { valueType: valueTypeLabel(raw) } };
|
|
39
49
|
if (raw !== raw.trim())
|
|
40
50
|
return inert("grammar");
|
|
41
51
|
const parsed = parsePermissionRule(raw);
|
|
@@ -94,11 +104,11 @@ export function compileAllowList(entries, behavior, roster) {
|
|
|
94
104
|
const rows = [];
|
|
95
105
|
const inert = [];
|
|
96
106
|
for (const entry of entries) {
|
|
97
|
-
const read =
|
|
107
|
+
const read = typeof entry !== "string" || !voided ? readAllowEntry(entry, behavior, roster) : { inert: "wildcard_all" };
|
|
98
108
|
if ("compiled" in read)
|
|
99
109
|
rows.push({ entry, compiled: read.compiled });
|
|
100
110
|
else
|
|
101
|
-
inert.push({ entry, reason: read.inert, ...read.detail });
|
|
111
|
+
inert.push({ entry: typeof entry === "string" ? entry : nonStringMemberLabel(entry), reason: read.inert, ...read.detail });
|
|
102
112
|
}
|
|
103
113
|
return { rows, inert };
|
|
104
114
|
}
|
|
@@ -153,6 +163,7 @@ export function autoStripped(compiled, call) {
|
|
|
153
163
|
export function autoStrippedRule(rule, call) {
|
|
154
164
|
return rule.behavior === "allow" && rule.tool === COMMAND_RULE_TOOL && autoStripped({ kind: "command", rule }, call);
|
|
155
165
|
}
|
|
166
|
+
const NO_LEADS = new Map();
|
|
156
167
|
function nameRowReaches(compiled, req) {
|
|
157
168
|
const covers = compiled.kind === "namespace" ? (n) => namespacedRuleNameCovers(compiled.pattern, n) : compiled.kind === "tool" || compiled.kind === "shell" ? (n) => n === compiled.name : undefined;
|
|
158
169
|
return covers !== undefined && answersTo(req, covers);
|
|
@@ -194,7 +205,7 @@ export function judgeAllowEntries(rows, req, behavior, ctx) {
|
|
|
194
205
|
if (pathRows.length > 0) {
|
|
195
206
|
const table = pathRows.map((r) => r.compiled.rule);
|
|
196
207
|
for (const name of laneToolNamesOf(req).filter((n) => ruleToolGrammarOf(n) === "path")) {
|
|
197
|
-
const answer = pathRuleLaneAnswer(table, { ...req, toolName: name }, { root: ctx.root, sessionId: ctx.sessionId,
|
|
208
|
+
const answer = pathRuleLaneAnswer(table, { ...req, toolName: name }, { root: ctx.root, sessionId: ctx.sessionId, anchorsOf: () => ctx.anchors, leads: callTargetReadingOf(req)?.leads ?? NO_LEADS });
|
|
198
209
|
const spoke = answer !== undefined && ("unreadable" in answer ? behavior === "deny" : "behavior" in answer && answer.behavior === behavior);
|
|
199
210
|
if (spoke) {
|
|
200
211
|
const rule = answer !== undefined && "rules" in answer ? answer.rules[0]?.rule : undefined;
|
|
@@ -307,6 +318,7 @@ const SHADOW_REASON = {
|
|
|
307
318
|
content_question: "the call is the engine's own question to a person",
|
|
308
319
|
hook: "a PreToolUse hook asks about this call — the deployment's own screening is answered by a person",
|
|
309
320
|
ancestor: "the task that delegated this one owns this question",
|
|
321
|
+
dangerous_delete: "this removal targets a protected location (the filesystem root, a top-level directory, a home directory, or a workspace directory or one of its ancestors) — it is cleared by confirming this call, never by an allow rule",
|
|
310
322
|
unverifiable_delete: "this removal could not be verified safe — it is cleared by confirming this call, never by an allow rule",
|
|
311
323
|
transcript_write: "this write targets the session transcript — it is cleared by confirming this call, never by an allow rule",
|
|
312
324
|
org_rule: "an organization policy rule requires approval for this call",
|
|
@@ -366,7 +378,22 @@ export function cardDisclosureOf(decision, fallbackMessage) {
|
|
|
366
378
|
}
|
|
367
379
|
export const ALLOWED_TOOL_NOT_GRANTED_CODE = "permission.allowed_tool_not_granted";
|
|
368
380
|
const NOTICE_ENTRIES_SHOWN = 5;
|
|
381
|
+
const UNSEEN_IN_ENTRY = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Zs}]/gu;
|
|
382
|
+
const ENTRY_RENDER_MAX = 122;
|
|
383
|
+
function escapedCodePoint(ch) {
|
|
384
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
385
|
+
const hex = cp.toString(16).padStart(4, "0");
|
|
386
|
+
return cp > 0xffff ? `\\u{${hex}}` : `\\u${hex}`;
|
|
387
|
+
}
|
|
388
|
+
export function renderListEntry(entry) {
|
|
389
|
+
const quoted = JSON.stringify(entry)
|
|
390
|
+
.replace(/ {2,}/g, (run) => "\\u0020".repeat(run.length))
|
|
391
|
+
.replace(UNSEEN_IN_ENTRY, (ch) => (ch === " " ? ch : escapedCodePoint(ch)));
|
|
392
|
+
return inlineUntrusted(quoted, ENTRY_RENDER_MAX);
|
|
393
|
+
}
|
|
369
394
|
export function inertEntryPhrase(e) {
|
|
395
|
+
if (e.valueType !== undefined)
|
|
396
|
+
return `a value that is ${e.valueType} (grammar: not a string)`;
|
|
370
397
|
const spelling = e.currentName !== undefined ? inlineUntrusted(e.currentName, 120) : undefined;
|
|
371
398
|
const why = (() => {
|
|
372
399
|
if (e.reason === "near_miss")
|
|
@@ -386,7 +413,7 @@ export function inertEntryPhrase(e) {
|
|
|
386
413
|
}
|
|
387
414
|
}
|
|
388
415
|
})();
|
|
389
|
-
return `${
|
|
416
|
+
return `${renderListEntry(e.entry)} (${why})`;
|
|
390
417
|
}
|
|
391
418
|
export function allowedToolsNotGrantedNotice(inert, sessionId) {
|
|
392
419
|
const where = (e) => (e.source === "skill" ? `skill ${inlineUntrusted(e.skill, 80)} ${e.list}` : e.source === "request" ? "this run's allowedTools" : "the deployment's allowedTools");
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* the ask). Both stay where they are and are consumed beside the origin, not folded into it.
|
|
26
26
|
*/
|
|
27
27
|
import type { PermissionResult } from "./tool-policy.js";
|
|
28
|
+
import type { ContainmentFace } from "../tools/fs/containment-face.js";
|
|
28
29
|
/**
|
|
29
30
|
* The closed set, in AUTHORITY order — the order {@link askOriginOf} resolves a fold in when several
|
|
30
31
|
* facts hold on one call (a hook ask folded over a policy ask, an org stamp over either). The order
|
|
@@ -448,8 +449,12 @@ export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
|
|
|
448
449
|
* `classifier_no_ruling` is a classifier station's question to a person because the auto-mode classification did not rule
|
|
449
450
|
* (`classifierNoRuling` — it could not run, or replied with no verdict): the denial-limit fallback's row (only a person
|
|
450
451
|
* answers it; the classifier is never asked again).
|
|
452
|
+
* `dangerous_delete` is a removal whose target is a protected location (`dangerousRemovalOf`: the filesystem root, a
|
|
453
|
+
* top-level directory, a home directory, or a workspace directory — the task root, the working directory, an additional
|
|
454
|
+
* working directory — or an ancestor of one): the reference implementation's dangerous-removal breaker, which no mode,
|
|
455
|
+
* allow rule or classifier answers. `unverifiable_delete` is its sibling for a target the command text cannot resolve.
|
|
451
456
|
*/
|
|
452
|
-
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "write_protection_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
457
|
+
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "write_protection_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
453
458
|
export type AskClass = (typeof ASK_CLASSES)[number];
|
|
454
459
|
/** Membership test for {@link AskClass} — the word crosses the wire on `tool_end.gate.disposition`. */
|
|
455
460
|
export declare function isAskClass(v: unknown): v is AskClass;
|
|
@@ -457,7 +462,8 @@ export declare function isAskClass(v: unknown): v is AskClass;
|
|
|
457
462
|
export interface ModeArbitrationRow {
|
|
458
463
|
/** The ask still reaches a person under `bypassPermissions` (the reference implementation's `bypassImmune`). */
|
|
459
464
|
readonly bypassImmune: boolean;
|
|
460
|
-
/** The auto-mode classifier may answer it
|
|
465
|
+
/** The auto-mode classifier may answer it — as the class stands on its own: the folded row of an ask
|
|
466
|
+
* ({@link foldArbitrationRow}) also takes the read face the ask was raised under. */
|
|
461
467
|
readonly classifierEligible: boolean;
|
|
462
468
|
/** The sandbox-admission leg may admit it (containment holds the effect). */
|
|
463
469
|
readonly sandboxAdmission: boolean;
|
|
@@ -473,6 +479,8 @@ export interface ModeArbitrationRow {
|
|
|
473
479
|
* sandbox columns are the fold of the two former per-word lane tables (only `probe` moved: the classifier may
|
|
474
480
|
* now answer it — the read boundary's unestablishable spelling is fed to it as a deny-table row instead), and
|
|
475
481
|
* `ruleMayClear` restates {@link ruleClearanceOf}'s conclusion per class (a real-approval bit is never rule-clearable).
|
|
482
|
+
* `probe`'s `classifierEligible` is the OPEN read face's reading: a narrowed (`roots`) face withholds the classifier from the class,
|
|
483
|
+
* folded where the ask's row is ({@link foldArbitrationRow}).
|
|
476
484
|
* `mode` is the permission mode's OWN question (`plan`'s two sentences, `decisionReason: "mode"`): the reference
|
|
477
485
|
* implementation puts it to a person past every allow rule and never to an automatic lane, and its bypass conversion
|
|
478
486
|
* releases it like any other non-immune ask. `unstated` is the reference implementation's passthrough conversion: an allow
|
|
@@ -503,6 +511,13 @@ export declare const MODE_ARBITRATION: {
|
|
|
503
511
|
readonly ruleMayClear: false;
|
|
504
512
|
readonly hookAskFloor: false;
|
|
505
513
|
};
|
|
514
|
+
readonly dangerous_delete: {
|
|
515
|
+
readonly bypassImmune: true;
|
|
516
|
+
readonly classifierEligible: false;
|
|
517
|
+
readonly sandboxAdmission: false;
|
|
518
|
+
readonly ruleMayClear: false;
|
|
519
|
+
readonly hookAskFloor: true;
|
|
520
|
+
};
|
|
506
521
|
readonly unverifiable_delete: {
|
|
507
522
|
readonly bypassImmune: true;
|
|
508
523
|
readonly classifierEligible: true;
|
|
@@ -683,6 +698,8 @@ export interface AskClassFacts extends AskOriginFacts {
|
|
|
683
698
|
readonly toolMarkSources?: ReadonlySet<ToolMarkSource>;
|
|
684
699
|
readonly a2aIsolatePeers?: boolean;
|
|
685
700
|
readonly ancestorClasses?: ReadonlySet<AskClass>;
|
|
701
|
+
/** The read face the leg this ask was raised on resolved ({@link ContainmentFace}); absent ⇒ none resolved (a leg with no hands). */
|
|
702
|
+
readonly readFace?: ContainmentFace;
|
|
686
703
|
}
|
|
687
704
|
/** The classification the arbitration reads: the whole set, the disclosed word (the set's first member in
|
|
688
705
|
* {@link ASK_CLASSES} order) and the row folded across the set. */
|
|
@@ -718,8 +735,10 @@ export declare function decisionAskClassesOf(d: Extract<PermissionResult, {
|
|
|
718
735
|
}>): Set<AskClass>;
|
|
719
736
|
/** Fold a class set onto one row: `bypassImmune` and `hookAskFloor` by OR (any immune member keeps the ask; any floored
|
|
720
737
|
* member floors a hook's ask), the three permissive switches by AND (one refusing member withholds the lane). An empty
|
|
721
|
-
* set is `ordinary`.
|
|
722
|
-
|
|
738
|
+
* set is `ordinary`. `readFace` is the face the ask was raised under: `roots` takes the classifier's lane from a set that
|
|
739
|
+
* holds a class the narrowed face withholds from it — the one place the face enters, so every reader of `classifierEligible`
|
|
740
|
+
* reads it already folded. */
|
|
741
|
+
export declare function foldArbitrationRow(classes: ReadonlySet<AskClass>, readFace?: ContainmentFace): ModeArbitrationRow;
|
|
723
742
|
/**
|
|
724
743
|
* The ONE class derivation: the decision half ({@link decisionAskClassesOf}) joined with the gate's own facts —
|
|
725
744
|
* the question tool, an ancestor's mark, the org layer's two arms, the persisted lane's unreadable call, the tool
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -117,7 +117,7 @@ const MANDATE_DISCLOSURE = {
|
|
|
117
117
|
write_protection_unresolved: "the write-protection judge could not resolve this write's target, so whether it lands on the protected table is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can",
|
|
118
118
|
};
|
|
119
119
|
export function mandateDisclosureOf(mandate) {
|
|
120
|
-
return mandate === undefined ? "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
|
|
120
|
+
return mandate === undefined ? "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
|
|
121
121
|
}
|
|
122
122
|
export function persistedRuleMandateOf(marks) {
|
|
123
123
|
const word = irreversibilityMandateOf(marks);
|
|
@@ -170,6 +170,7 @@ export const ASK_CLASSES = Object.freeze([
|
|
|
170
170
|
"content_question",
|
|
171
171
|
"hook",
|
|
172
172
|
"ancestor",
|
|
173
|
+
"dangerous_delete",
|
|
173
174
|
"unverifiable_delete",
|
|
174
175
|
"transcript_write",
|
|
175
176
|
"org_rule",
|
|
@@ -200,6 +201,7 @@ export const MODE_ARBITRATION = {
|
|
|
200
201
|
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
201
202
|
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
202
203
|
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
204
|
+
dangerous_delete: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
203
205
|
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
204
206
|
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
205
207
|
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
@@ -222,7 +224,8 @@ export const MODE_ARBITRATION = {
|
|
|
222
224
|
unstated: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: true, hookAskFloor: false },
|
|
223
225
|
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
224
226
|
};
|
|
225
|
-
|
|
227
|
+
const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]);
|
|
228
|
+
export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
226
229
|
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
227
230
|
export const MODE_RELEASE_REASONS = Object.freeze(["rule_unreadable"]);
|
|
228
231
|
const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS);
|
|
@@ -243,6 +246,8 @@ export function screenAskClassMembers(askClass, classes) {
|
|
|
243
246
|
}
|
|
244
247
|
export function decisionAskClassesOf(d) {
|
|
245
248
|
const out = new Set();
|
|
249
|
+
if (d.guard === "dangerous_delete")
|
|
250
|
+
out.add("dangerous_delete");
|
|
246
251
|
if (d.guard === "unverifiable_delete")
|
|
247
252
|
out.add("unverifiable_delete");
|
|
248
253
|
if (d.guard === "transcript_write")
|
|
@@ -273,10 +278,11 @@ export function decisionAskClassesOf(d) {
|
|
|
273
278
|
out.add(c);
|
|
274
279
|
return out;
|
|
275
280
|
}
|
|
276
|
-
export function foldArbitrationRow(classes) {
|
|
281
|
+
export function foldArbitrationRow(classes, readFace) {
|
|
277
282
|
if (classes.size === 0)
|
|
278
283
|
return MODE_ARBITRATION.ordinary;
|
|
279
|
-
|
|
284
|
+
const withheld = readFace === "roots" && [...classes].some((c) => CLASSIFIER_WITHHELD_ON_ROOTS.has(c));
|
|
285
|
+
let bypassImmune = false, classifierEligible = !withheld, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
280
286
|
for (const c of classes) {
|
|
281
287
|
const row = MODE_ARBITRATION[c];
|
|
282
288
|
bypassImmune ||= row.bypassImmune;
|
|
@@ -334,7 +340,7 @@ export function classifyAsk(d, facts) {
|
|
|
334
340
|
if (classes.size === 0)
|
|
335
341
|
classes.add("ordinary");
|
|
336
342
|
const askClass = ASK_CLASSES.find((c) => classes.has(c));
|
|
337
|
-
return { classes, askClass, row: foldArbitrationRow(classes) };
|
|
343
|
+
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace) };
|
|
338
344
|
}
|
|
339
345
|
export function headlessDenialMessage(toolName, askMessage) {
|
|
340
346
|
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|