@sema-agent/core 7.52.0 → 7.53.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 +194 -0
- package/README.md +3 -1
- package/dist/agents/cross-session-envelope.d.ts +8 -0
- package/dist/agents/cross-session-envelope.js +6 -0
- package/dist/agents/cross-session-judge.d.ts +21 -1
- package/dist/agents/cross-session-judge.js +8 -0
- package/dist/agents/cross-session-ref.d.ts +5 -0
- package/dist/agents/cross-session-ref.js +1 -0
- package/dist/agents/org-delivery-gate.d.ts +122 -0
- package/dist/agents/org-delivery-gate.js +69 -0
- package/dist/agents/peer-held-queue.d.ts +9 -4
- package/dist/agents/peer-held-queue.js +6 -5
- package/dist/agents/peer-session-drain.d.ts +35 -5
- package/dist/agents/peer-session-drain.js +271 -180
- package/dist/agents/read-inbox-tool.d.ts +7 -0
- package/dist/agents/read-inbox-tool.js +5 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +99 -72
- package/dist/agents/tool-filter.d.ts +18 -0
- package/dist/agents/tool-filter.js +6 -0
- package/dist/brain/anthropic.d.ts +6 -1
- package/dist/brain/anthropic.js +4 -4
- package/dist/brain/model-presets.js +2 -1
- package/dist/brain/route-adjudicator.d.ts +6 -0
- package/dist/brain/routing.d.ts +24 -1
- package/dist/brain/routing.js +54 -19
- package/dist/brain/timeout.js +5 -4
- package/dist/core/a2a.d.ts +5 -2
- package/dist/core/a2a.js +30 -13
- package/dist/core/allow-entry.d.ts +2 -1
- package/dist/core/allow-entry.js +2 -2
- package/dist/core/auto-compaction.d.ts +57 -0
- package/dist/core/auto-compaction.js +28 -2
- package/dist/core/checkpoint-store.d.ts +29 -20
- package/dist/core/checkpoint-store.js +3 -3
- package/dist/core/compliance.js +3 -1
- package/dist/core/effective-path-target.d.ts +25 -6
- package/dist/core/effective-path-target.js +24 -2
- package/dist/core/engine-notice.d.ts +13 -2
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-lanes.js +11 -13
- package/dist/core/gate-outcome.d.ts +11 -6
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hands-band-screen.d.ts +7 -0
- package/dist/core/hands-band-screen.js +32 -0
- package/dist/core/hooks.d.ts +7 -5
- package/dist/core/hooks.js +5 -3
- package/dist/core/locked-config.d.ts +84 -32
- package/dist/core/locked-config.js +128 -42
- package/dist/core/mcp-probe.js +1 -1
- package/dist/core/mcp-server-spec.d.ts +88 -10
- package/dist/core/mcp-server-spec.js +183 -33
- package/dist/core/mcp.d.ts +21 -14
- package/dist/core/mcp.js +55 -51
- package/dist/core/memory-engine/engine.d.ts +14 -19
- package/dist/core/memory-engine/engine.js +7 -5
- package/dist/core/mode-policy.d.ts +3 -18
- package/dist/core/mode-policy.js +2 -16
- package/dist/core/mods/kernel/dispatch.js +13 -15
- package/dist/core/mods/kernel/events.d.ts +9 -6
- package/dist/core/mods/kernel/events.js +2 -2
- package/dist/core/mods/kernel/receive.d.ts +39 -10
- package/dist/core/mods/kernel/receive.js +119 -54
- package/dist/core/mods/kernel/site-rules.d.ts +80 -15
- package/dist/core/mods/kernel/site-rules.js +232 -40
- package/dist/core/mods/stations.d.ts +0 -6
- package/dist/core/mods/stations.js +0 -7
- package/dist/core/mounted-mcp.d.ts +57 -0
- package/dist/core/mounted-mcp.js +71 -2
- package/dist/core/own-property-receipt.d.ts +13 -0
- package/dist/core/own-property-receipt.js +3 -1
- package/dist/core/parent-spec-seat.d.ts +2 -2
- package/dist/core/permission-rule-model.d.ts +58 -9
- package/dist/core/permission-rule-model.js +81 -7
- package/dist/core/permission-rule-org.d.ts +163 -33
- package/dist/core/permission-rule-org.js +205 -71
- package/dist/core/permission-rules.d.ts +7 -1
- package/dist/core/permission-rules.js +37 -8
- package/dist/core/retention.js +7 -2
- package/dist/core/retired-keys.d.ts +8 -2
- package/dist/core/retired-keys.js +12 -1
- package/dist/core/roles.js +60 -10
- package/dist/core/rule-lane-reads.js +1 -1
- package/dist/core/runner/contracts.d.ts +1 -0
- package/dist/core/runner/gate-exit.js +4 -3
- package/dist/core/runner/mcp-redial.d.ts +52 -10
- package/dist/core/runner/mcp-redial.js +38 -9
- package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
- package/dist/core/runner/permission-rule-lanes.js +22 -10
- package/dist/core/runner/person-path-rows.d.ts +3 -1
- package/dist/core/runner/person-path-rows.js +2 -2
- package/dist/core/runner/prepare-config-doors.d.ts +48 -2
- package/dist/core/runner/prepare-config-doors.js +161 -145
- package/dist/core/runner/prepare-defer-classify.js +4 -7
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.js +7 -5
- package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -1
- package/dist/core/runner/prepare-inherited-gate.js +2 -2
- package/dist/core/runner/prepare-listings.d.ts +1 -1
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
- package/dist/core/runner/prepare-memory-engine-session.js +19 -6
- package/dist/core/runner/prepare-memory.d.ts +3 -2
- package/dist/core/runner/prepare-memory.js +6 -2
- package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
- package/dist/core/runner/prepare-offload-wrappers.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +12 -12
- package/dist/core/runner/prepare-project-context.d.ts +1 -1
- package/dist/core/runner/prepare-project-context.js +6 -4
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-assembly.js +20 -16
- package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
- package/dist/core/runner/prepare-prompt-inputs.js +6 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
- package/dist/core/runner/prepare-protocol-tools.js +19 -12
- package/dist/core/runner/prepare-question-face.d.ts +4 -2
- package/dist/core/runner/prepare-question-face.js +4 -3
- package/dist/core/runner/prepare-task.js +10 -10
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
- package/dist/core/runner/prepare-wiring-manifest.js +8 -5
- package/dist/core/runner/resume-admission.d.ts +4 -1
- package/dist/core/runner/resume-admission.js +7 -5
- package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
- package/dist/core/runner/resume-internals-and-config.js +3 -8
- package/dist/core/runner/retired-runner-deps.d.ts +5 -6
- package/dist/core/runner/retired-runner-deps.js +1 -4
- package/dist/core/runner/run-leg.js +42 -12
- package/dist/core/runner/run-recovery-lanes.js +5 -3
- package/dist/core/runner/run-terminal-adoption.js +6 -5
- package/dist/core/runner/run-turn-boundary.js +9 -3
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/skills-seat.d.ts +15 -0
- package/dist/core/runner/skills-seat.js +57 -0
- package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
- package/dist/core/runner/sub-agent-deny-lists.js +7 -5
- package/dist/core/runner/synthetic-tools.d.ts +5 -2
- package/dist/core/runner/synthetic-tools.js +2 -12
- package/dist/core/runner-deps.d.ts +29 -14
- package/dist/core/session-policy-store.d.ts +19 -0
- package/dist/core/session-policy-store.js +22 -2
- package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
- package/dist/core/task-event.d.ts +38 -2
- package/dist/core/task-notification.js +2 -2
- package/dist/core/task-spec.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +3 -0
- package/dist/core/tool-policy.js +10 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +16 -0
- package/dist/core/untrusted-text.js +7 -2
- package/dist/core/user-attachment.d.ts +13 -2
- package/dist/core/user-attachment.js +47 -41
- package/dist/core/wiring-manifest.d.ts +4 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.d.ts +3 -0
- package/dist/engine/loop/agent-loop.d.ts +12 -1
- package/dist/engine/loop/agent-loop.js +17 -5
- package/dist/engine/loop/types.d.ts +7 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +12 -6
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.d.ts +24 -1
- package/dist/orchestration/goal.js +3 -2
- package/dist/prompt-assembly/artifact-store.d.ts +44 -9
- package/dist/prompt-assembly/artifact-store.js +38 -12
- package/dist/prompt-assembly/artifact.d.ts +12 -11
- package/dist/prompt-assembly/artifact.js +52 -21
- package/dist/prompt-assembly/assemble.d.ts +2 -0
- package/dist/prompt-assembly/assemble.js +2 -1
- package/dist/prompt-assembly/catalog/index.js +2 -1
- package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
- package/dist/prompt-assembly/catalog/memory.js +6 -0
- package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
- package/dist/prompt-assembly/composer.d.ts +2 -0
- package/dist/prompt-assembly/composer.js +1 -1
- package/dist/prompt-assembly/overlay.d.ts +21 -11
- package/dist/prompt-assembly/overlay.js +55 -17
- package/dist/prompt-assembly/select.d.ts +21 -6
- package/dist/prompt-assembly/select.js +39 -2
- package/dist/prompts/default.d.ts +0 -11
- package/dist/prompts/default.js +0 -3
- package/dist/stores/file/permission-rule-store.d.ts +1 -1
- package/dist/stores/file/permission-rule-store.js +2 -1
- package/dist/tools/artifact/artifact-tool.js +4 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +65 -5
- package/wire-changes/core@7.53.0.json +1946 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { escapeForDisclosure, parseRuleText, programRunReachOf, ruleReachesProgramRun } from "./permission-rule-model.js";
|
|
1
|
+
import { COMMAND_RULE_TOOL, PATH_RULE_FACES, escapeForDisclosure, parseRuleText, programRunReachOf, ruleReachesProgramRun, ruleToolGrammarOf } from "./permission-rule-model.js";
|
|
2
|
+
import { isNameableToolName, ruleSpellingOf } from "./permission-rule-syntax.js";
|
|
3
|
+
import { isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
|
|
2
4
|
import { writerOf } from "./permission-rule-store.js";
|
|
5
|
+
import { canonicalize } from "./canonical-json.js";
|
|
6
|
+
import { types } from "node:util";
|
|
7
|
+
import { describeFault, receive } from "./mods/kernel/receive.js";
|
|
8
|
+
import { DEFAULT_CAPS } from "./permission-rules.js";
|
|
3
9
|
export const ORG_UNAVAILABLE_DECISION_REASON = "org_unavailable";
|
|
4
10
|
export const ORG_ADJUDICATION_TIMEOUT_MS = 15_000;
|
|
5
11
|
export function settleOrgVerdictWithin(p, fallback, opts) {
|
|
@@ -48,7 +54,7 @@ export function createOrgRulePartition(cfg) {
|
|
|
48
54
|
let memoryState;
|
|
49
55
|
const readState = async () => {
|
|
50
56
|
const state = cfg.persistence !== undefined ? await cfg.persistence.readOrgState() : memoryState;
|
|
51
|
-
return state === undefined ? undefined :
|
|
57
|
+
return state === undefined ? undefined : orgStateCopyOf(state);
|
|
52
58
|
};
|
|
53
59
|
const writeState = async (state) => {
|
|
54
60
|
if (cfg.persistence !== undefined)
|
|
@@ -75,7 +81,7 @@ export function createOrgRulePartition(cfg) {
|
|
|
75
81
|
throw new Error(`the org snapshot source did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms`);
|
|
76
82
|
if ("e" in settled)
|
|
77
83
|
throw settled.e;
|
|
78
|
-
fetched =
|
|
84
|
+
fetched = orgSnapshotCopyOf(settled.v);
|
|
79
85
|
}
|
|
80
86
|
catch (err) {
|
|
81
87
|
fetchFailed = true;
|
|
@@ -152,6 +158,9 @@ export function createOrgRulePartition(cfg) {
|
|
|
152
158
|
if (!freshlyInstalled && !fetchFailed && fetched === undefined) {
|
|
153
159
|
disclosures.push("org snapshot source returned no snapshot — continuing on the last-known-good");
|
|
154
160
|
}
|
|
161
|
+
const degraded = describeUnreadableOrgRows(readListedOrgRules(installed.snapshot.rules).unreadable, installed.snapshot.revision);
|
|
162
|
+
if (degraded !== undefined)
|
|
163
|
+
disclosures.push(degraded);
|
|
155
164
|
return {
|
|
156
165
|
source: "org",
|
|
157
166
|
status: freshlyInstalled ? "fresh" : "last-known-good",
|
|
@@ -188,34 +197,187 @@ function persistedMarkIsCoherent(state) {
|
|
|
188
197
|
Number.isFinite(state.snapshot.revision) &&
|
|
189
198
|
state.revisionHighWater >= state.snapshot.revision);
|
|
190
199
|
}
|
|
191
|
-
function sameOrgPolicyContent(a, b) {
|
|
200
|
+
export function sameOrgPolicyContent(a, b) {
|
|
192
201
|
if (a.rules.length !== b.rules.length)
|
|
193
202
|
return false;
|
|
194
|
-
|
|
203
|
+
const policy = (r) => canonicalize(orgRowPolicyOf(r));
|
|
204
|
+
return a.rules.every((r, i) => policy(b.rules[i]) === policy(r));
|
|
195
205
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
const DISCLOSED_UNREADABLE_ROWS = 20;
|
|
207
|
+
function reachOfRowHead(text, behavior) {
|
|
208
|
+
const head = typeof text === "string" ? ruleSpellingOf(text)?.toolName : undefined;
|
|
209
|
+
if (head === undefined || !isNameableToolName(head))
|
|
210
|
+
return "every";
|
|
211
|
+
if (head === COMMAND_RULE_TOOL || isShellFaceToolName(head))
|
|
212
|
+
return { words: [COMMAND_RULE_TOOL], faces: [] };
|
|
213
|
+
if (ruleToolGrammarOf(head) === undefined)
|
|
214
|
+
return "every";
|
|
215
|
+
const column = Object.hasOwn(PATH_RULE_FACES, head) ? PATH_RULE_FACES[head]?.[behavior] : undefined;
|
|
216
|
+
return { words: [head], faces: column ?? [] };
|
|
217
|
+
}
|
|
218
|
+
function rowReaches(reach, words, face) {
|
|
219
|
+
if (reach === "every")
|
|
220
|
+
return true;
|
|
221
|
+
return reach.words.some((w) => words.includes(w)) || (face !== undefined && reach.faces.includes(face));
|
|
222
|
+
}
|
|
223
|
+
function listRefusalOf(rules) {
|
|
224
|
+
if (typeof rules === "object" && rules !== null) {
|
|
225
|
+
if (types.isProxy(rules))
|
|
226
|
+
return "rules is a Proxy, not an array";
|
|
227
|
+
const held = HELD_LIST_REFUSALS.get(rules);
|
|
228
|
+
if (held !== undefined)
|
|
229
|
+
return held;
|
|
209
230
|
}
|
|
210
|
-
|
|
231
|
+
if (!Array.isArray(rules))
|
|
232
|
+
return "rules is not an array";
|
|
233
|
+
const cap = DEFAULT_CAPS.maxRules;
|
|
234
|
+
return rules.length > cap ? `rules list has ${rules.length} entries; the cap is ${cap} — refused whole (not truncated)` : undefined;
|
|
235
|
+
}
|
|
236
|
+
export const ORG_ROW_POLICY_MEMBERS = Object.freeze(["rule", "behavior"]);
|
|
237
|
+
const ORG_SNAPSHOT_ENVELOPE_MEMBERS = Object.freeze(["revision", "fetchedAtMs", "signature"]);
|
|
238
|
+
const ORG_STATE_MEMBERS = Object.freeze(["revisionHighWater", "installedAtMs"]);
|
|
239
|
+
function scalarMembersOf(value, members) {
|
|
240
|
+
const held = {};
|
|
241
|
+
if (typeof value !== "object" || value === null)
|
|
242
|
+
return held;
|
|
243
|
+
for (const k of members) {
|
|
244
|
+
const v = Object.hasOwn(value, k) ? value[k] : undefined;
|
|
245
|
+
if (typeof v === "string" || typeof v === "boolean" || v === null || (typeof v === "number" && Number.isFinite(v)))
|
|
246
|
+
held[k] = v;
|
|
247
|
+
}
|
|
248
|
+
return held;
|
|
249
|
+
}
|
|
250
|
+
function orgRowPolicyOf(row) {
|
|
251
|
+
return scalarMembersOf(row, ORG_ROW_POLICY_MEMBERS);
|
|
252
|
+
}
|
|
253
|
+
const ORG_RULES_DEPTH = 2;
|
|
254
|
+
const ORG_RULES_RECEIPT = Object.freeze({ accessors: "call-once", depth: ORG_RULES_DEPTH, others: "copy", contain: true, isProxy: (v) => types.isProxy(v) });
|
|
255
|
+
export function orgRuleListOf(rules) {
|
|
256
|
+
const refused = listRefusalOf(rules);
|
|
257
|
+
if (refused !== undefined)
|
|
258
|
+
return { refused };
|
|
259
|
+
const received = receive(rules, ORG_RULES_RECEIPT);
|
|
260
|
+
if (!received.ok)
|
|
261
|
+
return { refused: `rules could not be received (${describeFault(received.fault)})` };
|
|
262
|
+
const copy = received.value;
|
|
263
|
+
return { rows: Array.from({ length: copy.length }, (_, i) => orgRowPolicyOf(Object.hasOwn(copy, i) ? copy[i] : undefined)) };
|
|
264
|
+
}
|
|
265
|
+
const HELD_LIST_REFUSALS = new WeakMap();
|
|
266
|
+
function heldRefusalOf(words) {
|
|
267
|
+
const held = Object.freeze({});
|
|
268
|
+
HELD_LIST_REFUSALS.set(held, words);
|
|
269
|
+
return held;
|
|
270
|
+
}
|
|
271
|
+
function orgSnapshotCopyOf(v) {
|
|
272
|
+
if (v === undefined)
|
|
273
|
+
return undefined;
|
|
274
|
+
if (typeof v === "object" && v !== null && types.isProxy(v))
|
|
275
|
+
throw new Error("the snapshot is a Proxy, not data");
|
|
276
|
+
const envelope = scalarMembersOf(v, ORG_SNAPSHOT_ENVELOPE_MEMBERS);
|
|
277
|
+
const rules = typeof v === "object" && v !== null ? v.rules : undefined;
|
|
278
|
+
const refusal = listRefusalOf(rules);
|
|
279
|
+
if (refusal !== undefined)
|
|
280
|
+
return { ...envelope, rules: heldRefusalOf(refusal) };
|
|
281
|
+
const list = orgRuleListOf(rules);
|
|
282
|
+
if ("refused" in list)
|
|
283
|
+
throw new Error(`the snapshot's ${list.refused}`);
|
|
284
|
+
return { ...envelope, rules: list.rows };
|
|
285
|
+
}
|
|
286
|
+
function orgStateCopyOf(state) {
|
|
287
|
+
if (typeof state === "object" && state !== null && types.isProxy(state))
|
|
288
|
+
throw new Error("the persisted org state is a Proxy, not data");
|
|
289
|
+
const snapshot = typeof state === "object" && state !== null ? state.snapshot : undefined;
|
|
290
|
+
return { ...scalarMembersOf(state, ORG_STATE_MEMBERS), snapshot: orgSnapshotCopyOf(snapshot ?? null) };
|
|
291
|
+
}
|
|
292
|
+
function readOrgRow(row, index) {
|
|
293
|
+
const { rule: text, behavior: said } = orgRowPolicyOf(row);
|
|
294
|
+
const behavior = said === "ask" ? "ask" : "deny";
|
|
295
|
+
const unreadable = (code, detail) => ({
|
|
296
|
+
kind: "unreadable",
|
|
297
|
+
index,
|
|
298
|
+
rule: typeof text === "string" ? text : text === undefined ? "<unreadable>" : escapeForDisclosure(text),
|
|
299
|
+
code,
|
|
300
|
+
...(detail !== undefined ? { detail } : {}),
|
|
301
|
+
behavior,
|
|
302
|
+
reach: reachOfRowHead(text, behavior),
|
|
303
|
+
});
|
|
304
|
+
if (typeof text !== "string" || text === "")
|
|
305
|
+
return unreadable("invalid.grammar", "no rule text");
|
|
306
|
+
if (said !== "deny" && said !== "ask")
|
|
307
|
+
return unreadable("invalid.grammar", said === undefined ? "no behavior" : `behavior "${escapeForDisclosure(said)}" is not deny or ask`);
|
|
308
|
+
const parsed = parseRuleText(text, said);
|
|
309
|
+
if ("reject" in parsed)
|
|
310
|
+
return unreadable(parsed.reject.code);
|
|
311
|
+
if (parsed.rule.tool !== COMMAND_RULE_TOOL)
|
|
312
|
+
return unreadable("unsupported.tool");
|
|
313
|
+
return { kind: "read", index, rule: text, behavior: said, parsed: parsed.rule };
|
|
314
|
+
}
|
|
315
|
+
export function readOrgRules(rules) {
|
|
316
|
+
const list = orgRuleListOf(rules);
|
|
317
|
+
if ("refused" in list)
|
|
318
|
+
return list;
|
|
319
|
+
const rows = list.rows.map((row, i) => readOrgRow(row, i + 1));
|
|
320
|
+
const unreadable = rows.filter((r) => r.kind === "unreadable");
|
|
321
|
+
return {
|
|
322
|
+
rows,
|
|
323
|
+
unreadable,
|
|
324
|
+
verdictFor(call) {
|
|
325
|
+
const words = typeof call.tool === "string" ? [call.tool] : call.tool;
|
|
326
|
+
const command = words.includes(COMMAND_RULE_TOOL) && typeof call.command === "string" ? call.command : undefined;
|
|
327
|
+
let ask;
|
|
328
|
+
let unjudged;
|
|
329
|
+
for (const row of rows) {
|
|
330
|
+
if (row.kind === "unreadable") {
|
|
331
|
+
if (!rowReaches(row.reach, words, call.face))
|
|
332
|
+
continue;
|
|
333
|
+
const verdict = { behavior: row.behavior, rule: row.rule, unreadable: unreadableOrgRowReason(row) };
|
|
334
|
+
if (row.behavior === "deny")
|
|
335
|
+
return verdict;
|
|
336
|
+
ask ??= verdict;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (command === undefined)
|
|
340
|
+
continue;
|
|
341
|
+
const outcome = programRunReachOf(row.parsed, command);
|
|
342
|
+
if (outcome.reach === "not_reached")
|
|
343
|
+
continue;
|
|
344
|
+
if (outcome.reach === "unreadable") {
|
|
345
|
+
unjudged ??= { behavior: "ask", rule: row.rule, unreadable: outcome.reason };
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (row.behavior === "deny")
|
|
349
|
+
return { behavior: "deny", rule: row.rule };
|
|
350
|
+
ask ??= { behavior: "ask", rule: row.rule };
|
|
351
|
+
}
|
|
352
|
+
return unjudged ?? ask;
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
export function unenforceableOrgRules(rules) {
|
|
357
|
+
return readListedOrgRules(rules).unreadable.map((u) => ({ rule: u.rule, code: u.code }));
|
|
358
|
+
}
|
|
359
|
+
function readListedOrgRules(rules) {
|
|
360
|
+
const reading = readOrgRules(rules);
|
|
361
|
+
if ("refused" in reading)
|
|
362
|
+
throw new TypeError(`the org rules cannot be read: ${reading.refused}`);
|
|
363
|
+
return reading;
|
|
364
|
+
}
|
|
365
|
+
export function unreadableOrgRowReason(row) {
|
|
366
|
+
return `row ${row.index} of the organization snapshot is not a rule this layer reads (${row.code}${row.detail !== undefined ? `: ${row.detail}` : ""}); republish it in the shell tool's command grammar`;
|
|
367
|
+
}
|
|
368
|
+
export function orgUnreadableClause(reason) {
|
|
369
|
+
return `which could not be read against it (${reason})`;
|
|
211
370
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
371
|
+
export function describeUnreadableOrgRows(unreadable, revision) {
|
|
372
|
+
if (unreadable.length === 0)
|
|
373
|
+
return undefined;
|
|
374
|
+
const named = unreadable
|
|
375
|
+
.slice(0, DISCLOSED_UNREADABLE_ROWS)
|
|
376
|
+
.map((u) => `row ${u.index} "${escapeForDisclosure(u.rule)}" (${u.code}${u.detail !== undefined ? `: ${u.detail}` : ""})`)
|
|
216
377
|
.join(", ");
|
|
217
|
-
const more =
|
|
218
|
-
return
|
|
378
|
+
const more = unreadable.length - DISCLOSED_UNREADABLE_ROWS;
|
|
379
|
+
return (`the org snapshot${revision !== undefined ? ` (revision ${escapeForDisclosure(revision)})` : ""} is served DEGRADED: ${unreadable.length} row(s) this layer cannot read — ${named}${more > 0 ? ` and ${more} more` : ""}; ` +
|
|
380
|
+
`each stands at its strictest reading over the calls it may reach (a deny row, or one whose behavior cannot be read, refuses them; an ask row asks a person), and every readable row stands as published — republish the named rows in the shell tool's command grammar`);
|
|
219
381
|
}
|
|
220
382
|
function validateOrgSnapshot(s, nowMs) {
|
|
221
383
|
if (typeof s.revision !== "number" || !Number.isFinite(s.revision))
|
|
@@ -225,61 +387,33 @@ function validateOrgSnapshot(s, nowMs) {
|
|
|
225
387
|
if (s.fetchedAtMs > nowMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS) {
|
|
226
388
|
return `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely`;
|
|
227
389
|
}
|
|
228
|
-
|
|
229
|
-
return "rules is not an array";
|
|
230
|
-
for (const r of s.rules) {
|
|
231
|
-
if (typeof r?.rule !== "string" || r.rule === "")
|
|
232
|
-
return "a rule entry carries no rule text";
|
|
233
|
-
if (r.behavior !== "deny" && r.behavior !== "ask") {
|
|
234
|
-
return `rule "${escapeForDisclosure(r.rule)}" carries behavior "${escapeForDisclosure(r.behavior)}" — the org layer has no allow bucket`;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const unenforceable = unenforceableOrgRules(s.rules);
|
|
238
|
-
const first = unenforceable[0];
|
|
239
|
-
if (first !== undefined) {
|
|
240
|
-
const rest = unenforceable.length > 1 ? ` (${unenforceable.length} in all — ${describeUnenforceable(unenforceable)})` : "";
|
|
241
|
-
return `rule "${escapeForDisclosure(first.rule)}" does not parse (${first.code}) — an unenforceable deny/ask must not stand as policy${rest}`;
|
|
242
|
-
}
|
|
243
|
-
return undefined;
|
|
390
|
+
return listRefusalOf(s.rules);
|
|
244
391
|
}
|
|
245
392
|
export function orgRuleVerdictFor(rules, call) {
|
|
246
|
-
|
|
247
|
-
let unreadable;
|
|
248
|
-
for (const r of rules) {
|
|
249
|
-
const parsed = parseRuleText(r.rule, r.behavior);
|
|
250
|
-
if ("reject" in parsed)
|
|
251
|
-
continue;
|
|
252
|
-
if (parsed.rule.tool !== call.tool)
|
|
253
|
-
continue;
|
|
254
|
-
const outcome = programRunReachOf(parsed.rule, call.command);
|
|
255
|
-
if (outcome.reach === "not_reached")
|
|
256
|
-
continue;
|
|
257
|
-
if (outcome.reach === "unreadable") {
|
|
258
|
-
unreadable ??= { behavior: "ask", rule: r.rule, unreadable: outcome.reason };
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
if (r.behavior === "deny")
|
|
262
|
-
return { behavior: "deny", rule: r.rule };
|
|
263
|
-
ask ??= { behavior: "ask", rule: r.rule };
|
|
264
|
-
}
|
|
265
|
-
return unreadable ?? ask;
|
|
393
|
+
return readListedOrgRules(rules).verdictFor(call);
|
|
266
394
|
}
|
|
267
395
|
export function orgRuleShadows(orgRules, rule) {
|
|
268
396
|
return compileOrgShadowPredicate(orgRules)(rule);
|
|
269
397
|
}
|
|
270
398
|
export function compileOrgShadowPredicate(orgRules) {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
continue;
|
|
275
|
-
const parsed = parseRuleText(d.rule, "deny");
|
|
276
|
-
if (!("reject" in parsed))
|
|
277
|
-
denies.push(parsed.rule);
|
|
278
|
-
}
|
|
399
|
+
const { rows } = readListedOrgRules(orgRules);
|
|
400
|
+
const denies = rows.flatMap((r) => (r.kind === "read" && r.behavior === "deny" ? [r.parsed] : []));
|
|
401
|
+
const standing = rows.flatMap((r) => (r.kind === "unreadable" && r.behavior === "deny" ? [r.reach] : []));
|
|
279
402
|
return (rule) => {
|
|
280
|
-
if (
|
|
403
|
+
if (standing.length > 0) {
|
|
404
|
+
const word = isShellFaceToolName(rule.tool) ? COMMAND_RULE_TOOL : rule.tool;
|
|
405
|
+
const target = pathTargetOf(rule.tool);
|
|
406
|
+
const faces = Object.hasOwn(PATH_RULE_FACES, rule.tool)
|
|
407
|
+
? (PATH_RULE_FACES[rule.tool]?.allow ?? [])
|
|
408
|
+
: target !== undefined
|
|
409
|
+
? [target.access === "read" ? "read" : "write"]
|
|
410
|
+
: [undefined];
|
|
411
|
+
if (standing.some((reach) => faces.some((face) => rowReaches(reach, [word], face))))
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
if (rule.tool !== COMMAND_RULE_TOOL || denies.length === 0)
|
|
281
415
|
return false;
|
|
282
|
-
return denies.some((d) =>
|
|
416
|
+
return denies.some((d) => ruleReachesProgramRun(d, rule.command));
|
|
283
417
|
};
|
|
284
418
|
}
|
|
285
419
|
export function isWritablePermissionRuleStore(store) {
|
|
@@ -47,7 +47,10 @@
|
|
|
47
47
|
* The NAME lane is the same shape for the tools of `NAME_RULE_FACES` (the `Skill` tool): a `Skill(<name>)` / `Skill(<prefix>:*)`
|
|
48
48
|
* deny or ask — the reference's own spelling — compiles through the same validator (the model's name grammar) and is judged on the
|
|
49
49
|
* one string the call names (`nameRuleSubjectOf`) with `nameRuleAdmits`, the reader the persisted rule store uses. The parameter
|
|
50
|
-
* spelling `Skill(skill:<name>)` those tools used to take is retired: refused loudly, its sentence naming the CC form.
|
|
50
|
+
* spelling `Skill(skill:<name>)` those tools used to take is retired: refused loudly, its sentence naming the CC form. A KEYED
|
|
51
|
+
* value tool (`Artifact(action:<word>)`) takes the same lane for content keyed by its own parameter (`nameGrammarReads`); its
|
|
52
|
+
* other arguments stay on the generic `param:pattern` lane. A call of it that names no value for the key as the tool reads it
|
|
53
|
+
* is refused by the key's deny or ask rule (`keyedValueAbsentReason`, the persisted lane's reading).
|
|
51
54
|
*
|
|
52
55
|
* 🔴 与 CC 的有意差(design/127 r2):
|
|
53
56
|
* - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样,且整个 policy 无 allow 臂。
|
|
@@ -126,6 +129,9 @@ export interface PermissionRuleCaps {
|
|
|
126
129
|
* 的热路径成本上界。 */
|
|
127
130
|
maxScalarValueChars: number;
|
|
128
131
|
}
|
|
132
|
+
/** The caps a rule set is held to unless the caller names its own — `maxRules` is the ONE bound on how many rules a set
|
|
133
|
+
* carries, read by the compiler here and by the org layer's list judgment (which refuses a longer list before it is copied). */
|
|
134
|
+
export declare const DEFAULT_CAPS: Readonly<PermissionRuleCaps>;
|
|
129
135
|
export interface PermissionRulePolicyOptions {
|
|
130
136
|
/** Invalid/unsupported rules: `"throw"`(default — 部署作者写错要立刻知道)aggregates ALL issues
|
|
131
137
|
* into one error; `"skip"` drops them (pair with {@link validatePermissionRules} for a report, and/or
|
|
@@ -2,9 +2,10 @@ import { attest } from "./tool-policy.js";
|
|
|
2
2
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
3
3
|
import { catalogRuleFaceOf, isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
|
|
4
4
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule, ruleSpellingOf } from "./permission-rule-syntax.js";
|
|
5
|
-
import { COMMAND_RULE_TOOL, NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, laneToolNamesOf, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleBasesNeeded
|
|
5
|
+
import { COMMAND_RULE_TOOL, NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, keyedValueAbsentReason, laneToolNamesOf, nameGrammarReads, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
|
|
6
6
|
import { pathFamilyOf } from "../tools/fs/safety.js";
|
|
7
|
-
import { callTargetReadingOf, callTargetViewsOf } from "./effective-path-target.js";
|
|
7
|
+
import { argsAsTheToolReads, callTargetReadingOf, callTargetViewsOf } from "./effective-path-target.js";
|
|
8
|
+
import { canonicalize, UNCANONICALIZABLE } from "./canonical-json.js";
|
|
8
9
|
import { bindPathRules, judgeRuleRows, policyAnchors, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
9
10
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
10
11
|
import { heldValues } from "./tool-name-readings.js";
|
|
@@ -24,7 +25,7 @@ function describeBehavior(v) {
|
|
|
24
25
|
default: return v === null ? "null" : "an object";
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
|
-
const DEFAULT_CAPS = {
|
|
28
|
+
export const DEFAULT_CAPS = {
|
|
28
29
|
maxRules: 256,
|
|
29
30
|
maxRuleLength: 512,
|
|
30
31
|
maxPatternLength: 256,
|
|
@@ -164,7 +165,7 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
164
165
|
continue;
|
|
165
166
|
}
|
|
166
167
|
const face = resolved.face;
|
|
167
|
-
if (
|
|
168
|
+
if (nameGrammarReads(toolName, content)) {
|
|
168
169
|
const parsedName = parseRuleText(text, verb);
|
|
169
170
|
if ("reject" in parsedName) {
|
|
170
171
|
bad(text, "invalid.command_rule", `${parsedName.reject.code}: ${parsedName.reject.message}`);
|
|
@@ -242,12 +243,16 @@ export function validatePermissionRules(rules, opts) {
|
|
|
242
243
|
function isPlainObjectArgs(args) {
|
|
243
244
|
return typeof args === "object" && args !== null && !Array.isArray(args);
|
|
244
245
|
}
|
|
245
|
-
function matchParamRules(rules,
|
|
246
|
+
function matchParamRules(rules, req, maxScalarValueChars) {
|
|
247
|
+
const args = req.args;
|
|
246
248
|
if (!isPlainObjectArgs(args))
|
|
247
249
|
return undefined;
|
|
248
250
|
for (const r of rules) {
|
|
249
|
-
if (!Object.hasOwn(args, r.param))
|
|
251
|
+
if (!Object.hasOwn(args, r.param)) {
|
|
252
|
+
if (readingAbsorbs(req, r.param, r.pattern))
|
|
253
|
+
return r;
|
|
250
254
|
continue;
|
|
255
|
+
}
|
|
251
256
|
const raw = args[r.param];
|
|
252
257
|
if (raw === undefined || raw === null)
|
|
253
258
|
continue;
|
|
@@ -262,6 +267,17 @@ function matchParamRules(rules, args, maxScalarValueChars) {
|
|
|
262
267
|
}
|
|
263
268
|
return undefined;
|
|
264
269
|
}
|
|
270
|
+
function readingAbsorbs(req, param, pattern) {
|
|
271
|
+
if (pattern.includes("*"))
|
|
272
|
+
return false;
|
|
273
|
+
try {
|
|
274
|
+
const read = canonicalize(argsAsTheToolReads(req, { ...req.args, [param]: pattern }));
|
|
275
|
+
return read !== UNCANONICALIZABLE && read === canonicalize(req.args);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
265
281
|
function ruleDenial(req, ruleText, source) {
|
|
266
282
|
return ruleDenialSentence({ source: "permission", tool: req.toolName, rule: ruleText, ...(source ? { origin: source } : {}) });
|
|
267
283
|
}
|
|
@@ -387,6 +403,16 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
387
403
|
}
|
|
388
404
|
return unreadable;
|
|
389
405
|
};
|
|
406
|
+
const keylessRefusal = (entry, req) => {
|
|
407
|
+
for (const r of [...entry.content.deny, ...entry.content.ask]) {
|
|
408
|
+
if (r.parsed.match !== "name" || nameRuleSubjectOf(r.parsed.tool, req.args) !== undefined)
|
|
409
|
+
continue;
|
|
410
|
+
const absent = keyedValueAbsentReason(r.parsed.tool);
|
|
411
|
+
if (absent !== undefined)
|
|
412
|
+
return ruleDenialSentence({ source: "permission", tool: req.toolName, rule: r.ruleText, ...(r.source ? { origin: r.source } : {}), detail: absent });
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
};
|
|
390
416
|
const unreadableAsk = (hit, lane, req) => ({
|
|
391
417
|
action: "ask",
|
|
392
418
|
message: `tool "${req.toolName}" needs approval: the call could not be read against the permission rule ${hit.rule.ruleText} (${hit.unreadable}) — a person must decide`,
|
|
@@ -433,11 +459,14 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
433
459
|
return face !== undefined ? attest(denied, req, { pathDenial: { rule: contentDeny.rule.ruleText, view: contentDeny.view, face } }) : denied;
|
|
434
460
|
}
|
|
435
461
|
if (entry) {
|
|
436
|
-
const paramDeny = matchParamRules(entry.param.deny, req
|
|
462
|
+
const paramDeny = matchParamRules(entry.param.deny, req, caps.maxScalarValueChars);
|
|
437
463
|
if (paramDeny) {
|
|
438
464
|
return { action: "deny", message: ruleDenial(req, paramDeny.ruleText, paramDeny.source) };
|
|
439
465
|
}
|
|
440
466
|
}
|
|
467
|
+
const keyless = entry !== undefined ? keylessRefusal(entry, req) : undefined;
|
|
468
|
+
if (keyless !== undefined)
|
|
469
|
+
return { action: "deny", message: keyless };
|
|
441
470
|
if (contentDeny?.unreadable !== undefined)
|
|
442
471
|
return unreadableAsk({ rule: contentDeny.rule, unreadable: contentDeny.unreadable }, "content_deny", req);
|
|
443
472
|
if (entry) {
|
|
@@ -458,7 +487,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
458
487
|
return { action: "ask", message: ruleAskMessage(contentAsk.rule.ruleText, contentAsk.rule.source), matchedAskRule: contentAsk.rule.ruleText };
|
|
459
488
|
}
|
|
460
489
|
if (entry) {
|
|
461
|
-
const paramAsk = matchParamRules(entry.param.ask, req
|
|
490
|
+
const paramAsk = matchParamRules(entry.param.ask, req, caps.maxScalarValueChars);
|
|
462
491
|
if (paramAsk) {
|
|
463
492
|
return { action: "ask", message: ruleAskMessage(paramAsk.ruleText, paramAsk.source), matchedAskRule: paramAsk.ruleText };
|
|
464
493
|
}
|
package/dist/core/retention.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
1
2
|
export const RETENTION_CASCADE_DOMAINS = Object.freeze(["checkpoints", "session-trees", "orphan-tool-results"]);
|
|
2
3
|
export const RETENTION_CASCADE_EXEMPT = Object.freeze(["session-pollution", "session-capture-optout", "lineage.json"]);
|
|
3
4
|
function codedError(code, message) {
|
|
@@ -7,8 +8,12 @@ function codedError(code, message) {
|
|
|
7
8
|
}
|
|
8
9
|
export function assertRetentionCapability(input) {
|
|
9
10
|
const { policy, locked, stores } = input;
|
|
11
|
+
const declared = policy;
|
|
12
|
+
if (declared !== undefined && (typeof declared !== "object" || declared === null || Array.isArray(declared))) {
|
|
13
|
+
throw codedError("config.retention_policy", `retentionPolicy must be an object carrying maxAgeDays (got ${declared === null ? "null" : Array.isArray(declared) ? "an array" : typeof declared}).`);
|
|
14
|
+
}
|
|
10
15
|
if (policy !== undefined && (!Number.isFinite(policy.maxAgeDays) || policy.maxAgeDays < 0)) {
|
|
11
|
-
throw codedError("config.retention_policy", `retentionPolicy.maxAgeDays must be a finite non-negative number of days (got ${
|
|
16
|
+
throw codedError("config.retention_policy", `retentionPolicy.maxAgeDays must be a finite non-negative number of days (got ${describeRejectedValue(policy.maxAgeDays)}).`);
|
|
12
17
|
}
|
|
13
18
|
const declaredRetention = (name, store) => {
|
|
14
19
|
const r = store.retention;
|
|
@@ -19,7 +24,7 @@ export function assertRetentionCapability(input) {
|
|
|
19
24
|
throw codedError("config.retention_capability", `store "${name}" declares an unparseable retention value ${JSON.stringify(r)} — expected "managed" or "none".`);
|
|
20
25
|
};
|
|
21
26
|
const declarations = stores
|
|
22
|
-
.filter((s) => s.store !== undefined)
|
|
27
|
+
.filter((s) => s.store !== undefined && s.store !== null)
|
|
23
28
|
.map(({ name, store }) => ({ name, retention: declaredRetention(name, store) }));
|
|
24
29
|
if (!locked || policy === undefined)
|
|
25
30
|
return;
|
|
@@ -46,7 +46,13 @@ export declare const RETIRED_SHELL_READ_BOUNDARY_KEYS: Readonly<Record<string, s
|
|
|
46
46
|
* decision that carries one is denied in the engine's own words, never read without it: the type retires a member only where
|
|
47
47
|
* a decision is a literal in an annotated position, and a `check` whose return type is inferred still compiles with it. */
|
|
48
48
|
export declare const RETIRED_DECISION_MEMBERS: Readonly<Record<string, string>>;
|
|
49
|
-
/**
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The `ToolPolicy` door — every policy the engine installs passes it (a task's or deployment's seat, a combined member, an inherited
|
|
51
|
+
* constraint): `undefined` (or `null`, the seat's other absence spelling) is no policy; a value that is not an object whose `check`
|
|
52
|
+
* is a function is no policy either, and refuses by name (`config.invalid_argument`, `detail.field: "ToolPolicy"`) — a policy the
|
|
53
|
+
* engine cannot call is never installed as if it were absent, which would run the leg with nothing the deployment meant to apply;
|
|
54
|
+
* a retired member refuses by name ({@link RETIRED_TOOL_POLICY_KEYS}).
|
|
55
|
+
*/
|
|
56
|
+
export declare function assertNoRetiredToolPolicyKeys(policy: unknown): void;
|
|
51
57
|
/** The `ShellReadBoundary` door ({@link RETIRED_SHELL_READ_BOUNDARY_KEYS}). */
|
|
52
58
|
export declare function assertNoRetiredShellReadBoundaryKeys(boundary: object): void;
|
|
@@ -45,8 +45,19 @@ export const RETIRED_DECISION_MEMBERS = Object.freeze({
|
|
|
45
45
|
guard: 'write `guards: ["<word>"]` instead (the same five words, as a list: one ask may stand on several integrity guards)',
|
|
46
46
|
});
|
|
47
47
|
export function assertNoRetiredToolPolicyKeys(policy) {
|
|
48
|
-
if (policy
|
|
48
|
+
if (policy === undefined || policy === null)
|
|
49
|
+
return;
|
|
50
|
+
const isRecord = typeof policy === "object" && !Array.isArray(policy);
|
|
51
|
+
if (isRecord)
|
|
49
52
|
assertNoRetiredKeys(policy, RETIRED_TOOL_POLICY_KEYS, "ToolPolicy");
|
|
53
|
+
const check = isRecord ? policy.check : undefined;
|
|
54
|
+
if (typeof check !== "function") {
|
|
55
|
+
const got = typeof policy !== "object" ? typeof policy : Array.isArray(policy) ? "an array" : `an object whose check is ${check === null ? "null" : typeof check}`;
|
|
56
|
+
const e = new Error(`ToolPolicy must be an object whose \`check\` is a function (got ${got}) — a policy the engine cannot call is refused rather than run as no policy; omit the seat for none.`);
|
|
57
|
+
e.code = "config.invalid_argument";
|
|
58
|
+
e.detail = { field: "ToolPolicy" };
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
50
61
|
}
|
|
51
62
|
export function assertNoRetiredShellReadBoundaryKeys(boundary) {
|
|
52
63
|
assertNoRetiredKeys(boundary, RETIRED_SHELL_READ_BOUNDARY_KEYS, "ShellReadBoundary");
|