@sema-agent/core 7.42.1 → 7.43.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 +100 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -3
- package/dist/agents/teacher.js +1 -1
- package/dist/agents/verify.js +2 -0
- package/dist/core/allow-entry.d.ts +50 -31
- package/dist/core/allow-entry.js +100 -37
- package/dist/core/ask-origin.d.ts +16 -5
- package/dist/core/ask-origin.js +11 -3
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/ask-unresolvable-notice.js +1 -1
- package/dist/core/auto-mode.d.ts +5 -4
- package/dist/core/auto-mode.js +3 -1
- package/dist/core/checkpoint-store.d.ts +18 -7
- package/dist/core/checkpoint-store.js +6 -3
- package/dist/core/child-clamp-seats.d.ts +15 -1
- package/dist/core/child-clamp-seats.js +6 -2
- package/dist/core/classifier-unavailable-arm.d.ts +42 -23
- package/dist/core/classifier-unavailable-arm.js +14 -3
- package/dist/core/decision-receipt.js +2 -0
- package/dist/core/gate-lanes.js +37 -29
- package/dist/core/hooks.d.ts +6 -1
- package/dist/core/hooks.js +3 -0
- package/dist/core/mode-policy.d.ts +5 -4
- package/dist/core/mode-policy.js +4 -0
- package/dist/core/permission-rule-consent.d.ts +3 -2
- package/dist/core/permission-rule-consent.js +25 -20
- package/dist/core/permission-rule-model.d.ts +47 -7
- package/dist/core/permission-rule-model.js +80 -6
- package/dist/core/permission-rules.d.ts +8 -2
- package/dist/core/permission-rules.js +26 -9
- package/dist/core/runner/classifier-ladder.d.ts +10 -8
- package/dist/core/runner/classifier-ladder.js +3 -1
- package/dist/core/runner/denial-limit-arms.d.ts +25 -8
- package/dist/core/runner/denial-limit-arms.js +24 -10
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +8 -2
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
- package/dist/core/runner/prepare-config-doors.d.ts +3 -0
- package/dist/core/runner/prepare-config-doors.js +6 -5
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
- package/dist/core/runner/prepare-inherited-gate.js +5 -3
- package/dist/core/runner/prepare-park-ask.d.ts +4 -4
- package/dist/core/runner/prepare-park-ask.js +2 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
- package/dist/core/runner/prepare-policy-chain.js +59 -38
- package/dist/core/runner/prepare-project-context.js +6 -2
- package/dist/core/runner/prepare-task.js +6 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
- package/dist/core/runner/prepare-wiring-manifest.js +8 -9
- package/dist/core/runner/resume-checkpoint-screen.js +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +3 -2
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/session-policy-store.d.ts +3 -3
- package/dist/core/session-policy-store.js +18 -4
- package/dist/core/skill-grant-binding.d.ts +44 -0
- package/dist/core/skill-grant-binding.js +22 -0
- package/dist/core/skill-grant.d.ts +59 -28
- package/dist/core/skill-grant.js +96 -23
- package/dist/core/task-spec.d.ts +14 -1
- package/dist/core/tighten-task-spec.d.ts +2 -0
- package/dist/core/tighten-task-spec.js +3 -0
- package/dist/core/tool-name-aliases.d.ts +35 -5
- package/dist/core/tool-name-aliases.js +29 -4
- package/dist/core/tool-policy.d.ts +10 -0
- package/dist/core/tool-policy.js +9 -2
- package/dist/core/tool-spec.d.ts +7 -0
- package/dist/core/wiring-manifest.d.ts +56 -14
- package/dist/core/wiring-manifest.js +29 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +4 -0
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +3 -1
- package/dist/orchestration/workflow-primitives.js +6 -6
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +11 -1
- package/wire-changes/core@7.43.0.json +985 -0
|
@@ -37,14 +37,29 @@ export function callPathFaceOf(call) {
|
|
|
37
37
|
export function faceAskSpeaksFor(face) {
|
|
38
38
|
return face !== undefined && Object.values(PATH_RULE_FACES).some((columns) => columns.ask?.includes(face) === true);
|
|
39
39
|
}
|
|
40
|
+
export const NAME_RULE_FACES = Object.freeze({
|
|
41
|
+
Skill: Object.freeze({ param: "skill", params: Object.freeze(["skill", "args"]) }),
|
|
42
|
+
});
|
|
40
43
|
export function ruleToolGrammarOf(toolName) {
|
|
41
44
|
if (toolName === COMMAND_RULE_TOOL)
|
|
42
45
|
return "command";
|
|
46
|
+
if (Object.hasOwn(NAME_RULE_FACES, toolName))
|
|
47
|
+
return "name";
|
|
43
48
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
44
49
|
}
|
|
50
|
+
export function nameRuleSubjectOf(toolName, args) {
|
|
51
|
+
const face = Object.hasOwn(NAME_RULE_FACES, toolName) ? NAME_RULE_FACES[toolName] : undefined;
|
|
52
|
+
if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
|
|
53
|
+
return undefined;
|
|
54
|
+
const value = args[face.param];
|
|
55
|
+
return typeof value === "string" ? value : undefined;
|
|
56
|
+
}
|
|
45
57
|
export function laneToolNamesOf(req) {
|
|
46
58
|
return heldValues("policy.persistedGrammar", { get: (n) => (ruleToolGrammarOf(n) !== undefined ? n : undefined) }, req);
|
|
47
59
|
}
|
|
60
|
+
function isCommandMatch(match) {
|
|
61
|
+
return match === "exact" || match === "prefix" || match === "wildcard";
|
|
62
|
+
}
|
|
48
63
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
49
64
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
50
65
|
"node", "deno", "bun", "python", "python2", "python3", "perl", "ruby", "php", "osascript",
|
|
@@ -247,8 +262,11 @@ export function parseRuleText(text, behavior) {
|
|
|
247
262
|
return reject("invalid.grammar", `"${text}" is not a Tool(content) rule — a bare tool name claims the whole tool and is not a command rule`);
|
|
248
263
|
}
|
|
249
264
|
if (parsed.toolName !== COMMAND_RULE_TOOL) {
|
|
250
|
-
|
|
251
|
-
|
|
265
|
+
const grammar = ruleToolGrammarOf(parsed.toolName);
|
|
266
|
+
if (grammar === "name")
|
|
267
|
+
return parseNameRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
|
|
268
|
+
if (grammar !== "path") {
|
|
269
|
+
return reject("unsupported.tool", `only Bash command rules, rules on path-targeting tools (Read, Edit, Write, …) and Skill name rules have a rule form in this version (got "${parsed.toolName}")`);
|
|
252
270
|
}
|
|
253
271
|
return parsePathRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
|
|
254
272
|
}
|
|
@@ -303,9 +321,46 @@ export function parseRuleText(text, behavior) {
|
|
|
303
321
|
export function formatRuleText(command, match, tool) {
|
|
304
322
|
if (match === "subpath")
|
|
305
323
|
return `${tool}(//${command.startsWith("/") ? command.slice(1) : command}/**)`;
|
|
306
|
-
if (match === "path")
|
|
324
|
+
if (match === "path" || match === "name")
|
|
307
325
|
return `${tool}(${command})`;
|
|
308
|
-
return `${tool}(${command}${match === "prefix" ? ":*" : match === "wildcard" ? " *" : ""})`;
|
|
326
|
+
return `${tool}(${command}${match === "prefix" || match === "name_prefix" ? ":*" : match === "wildcard" ? " *" : ""})`;
|
|
327
|
+
}
|
|
328
|
+
function parseNameRuleContent(text, tool, content, behavior, verifyCanonical = true) {
|
|
329
|
+
const face = NAME_RULE_FACES[tool];
|
|
330
|
+
const bare = content.startsWith("/") ? content.slice(1) : content;
|
|
331
|
+
const colon = bare.indexOf(":");
|
|
332
|
+
if (face !== undefined && colon > 0 && face.params.includes(bare.slice(0, colon).trim())) {
|
|
333
|
+
return reject("invalid.grammar", `"${text}" is the parameter spelling, which a ${tool} rule no longer has — a ${tool} rule names the ${face.param} itself: ${tool}(<name>) for one, ${tool}(<name>:*) for every ${face.param} whose name begins with <name>`);
|
|
334
|
+
}
|
|
335
|
+
const open = bare.endsWith(":*") || bare.endsWith(" *");
|
|
336
|
+
const body = open ? bare.slice(0, -2) : bare;
|
|
337
|
+
const match = open ? "name_prefix" : "name";
|
|
338
|
+
if (body === "" || body.trim() !== body) {
|
|
339
|
+
return reject("invalid.grammar", `the name in "${text}" is empty or carries surrounding whitespace — a ${tool} rule names the ${face?.param ?? "name"} exactly as it is spelled, so the spelling must be the one meant`);
|
|
340
|
+
}
|
|
341
|
+
if (body.includes("*")) {
|
|
342
|
+
return reject("unsupported.wildcard", `a \`*\` inside the name of "${text}" matches anything at that position, which has no rule form — only the trailing \`<name>:*\` prefix form does`);
|
|
343
|
+
}
|
|
344
|
+
const canonical = formatRuleText(body, match, tool);
|
|
345
|
+
const back = parsePermissionRule(canonical);
|
|
346
|
+
if (back.toolName !== tool || back.ruleContent !== (open ? `${body}:*` : body)) {
|
|
347
|
+
return reject("invalid.grammar", `the name in "${text}" has no spelling the rule syntax reads back (a backslash or an unbalanced parenthesis) — no ${tool} rule can name it`);
|
|
348
|
+
}
|
|
349
|
+
const rule = { behavior, rule: canonical, tool, match, command: body };
|
|
350
|
+
if (verifyCanonical) {
|
|
351
|
+
const again = parseNameRuleContent(canonical, tool, back.ruleContent, behavior, false);
|
|
352
|
+
if (!("rule" in again) || again.rule.rule !== canonical || again.rule.match !== match || again.rule.command !== body) {
|
|
353
|
+
return reject("invalid.grammar", `"${text}" has no canonical spelling that reads back as itself ("${canonical}" does not) — no ${tool} rule can name it exactly`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { rule };
|
|
357
|
+
}
|
|
358
|
+
export function nameRuleAdmits(rule, name) {
|
|
359
|
+
if (rule.match === "name")
|
|
360
|
+
return name === rule.command;
|
|
361
|
+
if (rule.match === "name_prefix")
|
|
362
|
+
return name.startsWith(rule.command);
|
|
363
|
+
return false;
|
|
309
364
|
}
|
|
310
365
|
function parsePathRuleContent(text, tool, content, behavior) {
|
|
311
366
|
const dirForm = /^\/\/(.+)\/\*\*$/.exec(content);
|
|
@@ -508,7 +563,7 @@ export const PROGRAM_RUN_REACHES = Object.freeze(["reached", "not_reached", "unr
|
|
|
508
563
|
const REACHED = { reach: "reached" };
|
|
509
564
|
const NOT_REACHED = { reach: "not_reached" };
|
|
510
565
|
export function programRunReachOf(rule, command) {
|
|
511
|
-
if (rule.match
|
|
566
|
+
if (!isCommandMatch(rule.match))
|
|
512
567
|
return NOT_REACHED;
|
|
513
568
|
const body = readRuleBody(rule.command);
|
|
514
569
|
if (body === undefined)
|
|
@@ -594,7 +649,7 @@ function readCommandMemo(command) {
|
|
|
594
649
|
return lastRead.shape;
|
|
595
650
|
}
|
|
596
651
|
function admitsUnder(rule, command, reading) {
|
|
597
|
-
if (rule.match
|
|
652
|
+
if (!isCommandMatch(rule.match))
|
|
598
653
|
return false;
|
|
599
654
|
const shape = ruleLaneShapeOf(command, reading);
|
|
600
655
|
if ("reject" in shape)
|
|
@@ -894,6 +949,25 @@ export function adjudicatePersistedPathRules(rules, call, targets, bases) {
|
|
|
894
949
|
}
|
|
895
950
|
return undefined;
|
|
896
951
|
}
|
|
952
|
+
export function adjudicatePersistedNameRules(rules, call) {
|
|
953
|
+
if (call.name === undefined)
|
|
954
|
+
return undefined;
|
|
955
|
+
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
956
|
+
for (const rule of rules) {
|
|
957
|
+
if (eligiblePersisted(rule, call, behavior) && nameRuleAdmits(rule, call.name))
|
|
958
|
+
return { behavior, rules: [rule] };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
export function suggestRuleForName(tool, name) {
|
|
964
|
+
if (ruleToolGrammarOf(tool) !== "name")
|
|
965
|
+
return undefined;
|
|
966
|
+
const parsed = parseRuleText(formatRuleText(name, "name", tool), "allow");
|
|
967
|
+
if (!("rule" in parsed) || parsed.rule.match !== "name" || parsed.rule.command !== name)
|
|
968
|
+
return undefined;
|
|
969
|
+
return { kind: "single", rule: parsed.rule.rule, match: "name", command: name };
|
|
970
|
+
}
|
|
897
971
|
export function segmentCoverageOf(command, rules, call) {
|
|
898
972
|
const folded = foldSpacing(command);
|
|
899
973
|
if (folded === undefined)
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
* A `Read(...)` / `Edit(...)` deny or ask reaches the calls on its behavior's FACES (`PATH_RULE_FACES`), not only
|
|
39
39
|
* its own tool: a `Read` ask asks for a Grep / Glob rooted in its path, judged on that call's own target.
|
|
40
40
|
*
|
|
41
|
+
* The NAME lane is the same shape for the tools of `NAME_RULE_FACES` (the `Skill` tool): a `Skill(<name>)` / `Skill(<prefix>:*)`
|
|
42
|
+
* deny or ask — the reference's own spelling — compiles through the same validator (the model's name grammar) and is judged on the
|
|
43
|
+
* one string the call names (`nameRuleSubjectOf`) with `nameRuleAdmits`, the reader the persisted rule store uses. The parameter
|
|
44
|
+
* spelling `Skill(skill:<name>)` those tools used to take is retired: refused loudly, its sentence naming the CC form.
|
|
45
|
+
*
|
|
41
46
|
* 🔴 与 CC 的有意差(design/127 r2):
|
|
42
47
|
* - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样,且整个 policy 无 allow 臂。
|
|
43
48
|
* - CC 对尾缀垃圾静默降级为裸名(Lh `n !== len-1`)—— `parsePermissionRule` 保持 CC parity,但
|
|
@@ -75,8 +80,9 @@ export interface PermissionRuleIssue {
|
|
|
75
80
|
/** A `behavior` outside the closed set `deny | ask` (a JS caller's typo, an undefined member, a boxed string): refused —
|
|
76
81
|
* a rule that matched nothing because its verb was misspelled would be a deny that silently never fires. */
|
|
77
82
|
| "invalid.behavior"
|
|
78
|
-
/** A content-form
|
|
79
|
-
* mid-pattern star, control characters…)
|
|
83
|
+
/** A content-form deny/ask rule the ONE rule validator refuses — the shell's command grammar (a redirection, a substitution,
|
|
84
|
+
* a mid-pattern star, control characters…), a path tool's path grammar, a name tool's name grammar (the retired parameter
|
|
85
|
+
* spelling `Skill(skill:<name>)`, an empty name…) — the message carries the validator's own code and sentence. */
|
|
80
86
|
| "invalid.command_rule"
|
|
81
87
|
/** An `allowedTools` value (the request's `TaskSpec.allowedTools`, the deployment's `RunnerDeps.allowedTools`) that is
|
|
82
88
|
* not a list of strings: one issue for a non-list, one per element that is not a string (`rule` is empty — there is
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
|
+
import { splitNamespacedName } from "./tool-name-aliases.js";
|
|
2
3
|
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
3
4
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
4
|
-
import { PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, parseRuleText, pathRuleHeadOf, pathRuleReachOf, programRunReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
|
|
5
|
+
import { NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, pathRuleHeadOf, pathRuleReachOf, programRunReachOf, ruleBasesNeeded, ruleToolGrammarOf } from "./permission-rule-model.js";
|
|
5
6
|
import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/safety.js";
|
|
6
7
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
8
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
@@ -58,15 +59,10 @@ export function wildcardMatch(pattern, value) {
|
|
|
58
59
|
return p === pattern.length;
|
|
59
60
|
}
|
|
60
61
|
function parseNamespacedRuleName(name) {
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
62
|
+
const split = splitNamespacedName(name);
|
|
63
|
+
if (split === undefined || split.peer === "")
|
|
63
64
|
return undefined;
|
|
64
|
-
|
|
65
|
-
const peer = segments[0];
|
|
66
|
-
if (peer === undefined || peer === "")
|
|
67
|
-
return undefined;
|
|
68
|
-
const rest = segments.slice(1);
|
|
69
|
-
return rest.length > 0 ? { protocol: ns.id, peer, tool: rest.join("__") } : { protocol: ns.id, peer };
|
|
65
|
+
return split.tool !== undefined ? { protocol: split.ns.id, peer: split.peer, tool: split.tool } : { protocol: split.ns.id, peer: split.peer };
|
|
70
66
|
}
|
|
71
67
|
export function namespacedRuleNameCovers(ruleName, toolName) {
|
|
72
68
|
const rule = parseNamespacedRuleName(ruleName);
|
|
@@ -215,6 +211,21 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
215
211
|
continue;
|
|
216
212
|
}
|
|
217
213
|
const face = resolved.face;
|
|
214
|
+
if (ruleToolGrammarOf(toolName) === "name") {
|
|
215
|
+
const parsedName = parseRuleText(text, verb);
|
|
216
|
+
if ("reject" in parsedName) {
|
|
217
|
+
bad(text, "invalid.command_rule", `${parsedName.reject.code}: ${parsedName.reject.message}`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const param = NAME_RULE_FACES[toolName].param;
|
|
221
|
+
if (face?.params !== undefined && !face.params.includes(param)) {
|
|
222
|
+
bad(text, "unsupported.param_unknown", `"${param}" is not a parameter of ${toolName} on this leg (it carries ${[...face.params].sort().join(", ")}) — a ${toolName} rule is read against it, so the rule would compile and never match`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
entry.content[verb].push({ parsed: parsedName.rule, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
226
|
+
byTool.set(toolName, entry);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
218
229
|
if (face !== undefined && face.pathTargeting === true && colon <= 0) {
|
|
219
230
|
const parsedPath = parseRuleText(text, verb);
|
|
220
231
|
if ("reject" in parsedPath) {
|
|
@@ -362,6 +373,12 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
362
373
|
const command = isPlainObjectArgs(req.args) ? req.args[commandParam] : undefined;
|
|
363
374
|
let unreadable;
|
|
364
375
|
for (const r of lane) {
|
|
376
|
+
if (r.parsed.match === "name" || r.parsed.match === "name_prefix") {
|
|
377
|
+
const named = nameRuleSubjectOf(r.parsed.tool, req.args);
|
|
378
|
+
if (named !== undefined && nameRuleAdmits(r.parsed, named))
|
|
379
|
+
return { rule: r };
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
365
382
|
if (r.parsed.match === "subpath" || r.parsed.match === "path") {
|
|
366
383
|
if (target === undefined || target.kind === "refused")
|
|
367
384
|
continue;
|
|
@@ -110,17 +110,19 @@ export declare function announceClassifierRoleDefaulted(args: {
|
|
|
110
110
|
runId?: string;
|
|
111
111
|
}): void;
|
|
112
112
|
/**
|
|
113
|
-
* The `classifier.unavailable` notice — a classification that could not RULE (the classifier leg failed,
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* `
|
|
113
|
+
* The `classifier.unavailable` notice — a classification that could not RULE (the classifier leg failed, its per-level
|
|
114
|
+
* budget ran out, or it replied with no verdict — `parse_error`; the code name predates that third cause) and where the
|
|
115
|
+
* call went: `"asked"` = on down the ask chain to this leg's approval seat (a person or a durable park decides; the
|
|
116
|
+
* handed-on ask carries `requiresRealApproval` and `classifierNoRuling`, so no automatic lane answers it), `"denied"` =
|
|
117
|
+
* refused (no approval seat: nobody to ask — a reply with no verdict is then also counted on the denial limit). Audience `user`: the refusal is the session user's own
|
|
117
118
|
* call, and on a lane with no approval face (`-p`) this line is the only place they hear why it did not run. Once
|
|
118
119
|
* per classification that could not answer, from both classifier stations (the leg's gate and an ancestor's frozen
|
|
119
|
-
* classifier in the policy fold) — a call judged by more than one classifier can carry one line per classifier.
|
|
120
|
-
*
|
|
120
|
+
* classifier in the policy fold) — a call judged by more than one classifier can carry one line per classifier. The line
|
|
121
|
+
* itself is never the count: the unavailable form is never counted; a reply with no verdict refused for want of a seat is
|
|
122
|
+
* counted by the station's ruling path, not by this line.
|
|
121
123
|
*
|
|
122
|
-
* `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is the
|
|
123
|
-
* `
|
|
124
|
+
* `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is why the classification did not rule,
|
|
125
|
+
* an `AutoModeFallbackCause` word (`timeout` / `error`, or `parse_error` for a reply with no verdict). The sentence names the seat the station forked on — `leg` (this
|
|
124
126
|
* run's own approval seat) or `ancestor` (the frozen approver of the ancestor layer whose classifier it was) — and, on a
|
|
125
127
|
* timeout, the budget the classifier actually held (`budgetMs`, the decider's {@link AutoModeDecider.timeoutMs}; a
|
|
126
128
|
* hand-built decider states none, and the sentence names the knob alone).
|
|
@@ -98,7 +98,9 @@ export function classifierUnavailableNotice(args) {
|
|
|
98
98
|
const whose = args.station === "ancestor" ? "the classifier of the ancestor layer that asked" : "the classifier";
|
|
99
99
|
const why = args.cause === "timeout"
|
|
100
100
|
? `${whose} did not answer within its per-level budget (${args.budgetMs !== undefined ? `${args.budgetMs} ms, ` : ""}RunnerDeps.autoMode.timeoutMs)`
|
|
101
|
-
:
|
|
101
|
+
: args.cause === "parse_error"
|
|
102
|
+
? args.station === "ancestor" ? `${whose} replied without a verdict` : "the classifier's reply carried no verdict"
|
|
103
|
+
: `${whose} could not run (error)`;
|
|
102
104
|
const next = args.disposition === "asked"
|
|
103
105
|
? args.station === "ancestor"
|
|
104
106
|
? "so the call was handed on to that layer's approver rather than refused by the classifier."
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type DenialLimitFallback, type UnarmedDenialLimitFallback } from "../auto-mode.js";
|
|
1
|
+
import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type AutoModeFallbackCause, type DenialLimitFallback, type UnarmedDenialLimitFallback } from "../auto-mode.js";
|
|
2
2
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
3
3
|
import { type ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
|
|
4
4
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
@@ -114,6 +114,7 @@ export interface AskRequestCarry {
|
|
|
114
114
|
askClass?: AskClass;
|
|
115
115
|
classes?: readonly AskClass[];
|
|
116
116
|
denialLimitFallback?: DenialLimitFallback;
|
|
117
|
+
classifierNoRuling?: AutoModeFallbackCause;
|
|
117
118
|
}
|
|
118
119
|
/** The GATE's own mint station: every member is read off the surviving decision, which is the gate's
|
|
119
120
|
* OWN snapshot (its stamp point spreads the policy's object and stamps the word and the class onto the copy),
|
|
@@ -154,8 +155,9 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
154
155
|
* `classification` is the class set of the ask handed on, under the same station facts: on the two unchanged-ask
|
|
155
156
|
* branches the one read the exclusion judged by, on the unavailable and bound branches a read of the object this
|
|
156
157
|
* station minted (each adds a real-approval bit, which the set must reflect). */
|
|
157
|
-
/** `ask` is the incoming ask unchanged (the arm's receipt snapshot of the ancestor's decision), or —
|
|
158
|
-
* approver — that ask
|
|
158
|
+
/** `ask` is the incoming ask unchanged (the arm's receipt snapshot of the ancestor's decision), or — a classification that did not rule, with a
|
|
159
|
+
* live frozen approver — that ask carrying why (`classifierNoRuling`, the sentence appended); without one the unavailable form is a `deny`
|
|
160
|
+
* whose attested cause reads `unavailable`. */
|
|
159
161
|
| {
|
|
160
162
|
kind: "resolve";
|
|
161
163
|
ask: A;
|
|
@@ -163,6 +165,7 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
163
165
|
mintedHere: boolean;
|
|
164
166
|
origin: AskOrigin;
|
|
165
167
|
classification: AskClassification;
|
|
168
|
+
noRulingHere?: true;
|
|
166
169
|
};
|
|
167
170
|
/**
|
|
168
171
|
* The ancestor's frozen classifier, run BEFORE its frozen approver in the ancestor's own gate order,
|
|
@@ -170,10 +173,11 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
170
173
|
* auto-allow (and the streak resets); `block` below the bound ⇒ deny, the exact verdict the ancestor's
|
|
171
174
|
* gate would have produced; `block` AT the bound ⇒ the ask is re-spoken in the classifier's voice as
|
|
172
175
|
* the fallback (`decisionReason:"classifier"`, `requiresRealApproval`, the member carrying counts +
|
|
173
|
-
* window) for the arm to resolve at the frozen approver;
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
176
|
+
* window) for the arm to resolve at the frozen approver; a classification that did not rule (`unavailable`, or a reply
|
|
177
|
+
* with no verdict) ⇒ the gate's own exit (`classifierNoRulingArm`) on the frozen approver — a live one resolves the incoming
|
|
178
|
+
* ask carrying why (never counted); none ⇒ the unavailable form is a DENY that says so (CC 2.1.250 form, never counted) and
|
|
179
|
+
* a reply with no verdict takes the block path with the CC parse-failure sentence (counted); a CONFLICTING reply takes the
|
|
180
|
+
* block path too; excluded / no classifier ⇒ the incoming ask, unchanged. The member may ALREADY be on the incoming
|
|
177
181
|
* ask (a deeper ancestor's fallback, minted inside the nested fold — the case the exclusion names):
|
|
178
182
|
* it rides to the approver and the headless arm unchanged, whichever tracker minted it; only a
|
|
179
183
|
* tracker that counted THIS streak is reset by the person's later allow (`mintedHere`).
|
|
@@ -203,11 +207,23 @@ export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
|
203
207
|
/** The rewrite the arm's allow would have carried (the ask's own `updatedInput`, the re-check's edited
|
|
204
208
|
* args) — handed IN so the allow is minted here, attested, once; absent ⇒ a bare allow. */
|
|
205
209
|
allowRewrite?: unknown;
|
|
206
|
-
/** An `unavailable` verdict's fork facts (`
|
|
210
|
+
/** An `unavailable` verdict's fork facts (`classifierNoRulingArm`): is the frozen approver live; who hears it. */
|
|
207
211
|
unavailableSeat?: ClassifierUnavailableSeat;
|
|
208
212
|
/** The station's facts for this call (the base with this task's tool seat); absent ⇒ {@link INHERITED_STATION_FACTS}. */
|
|
209
213
|
facts?: AskClassFacts;
|
|
210
214
|
}): Promise<InheritedClassifierJudgment<A>>;
|
|
215
|
+
/** The fallback an inherited station hands its frozen approver, minted ONCE for both ways a bound is reached (a counted ruling
|
|
216
|
+
* refusal; a no-ruling card the approver's POLICY refused, {@link settleNoRulingAsk}) — its own object, word and class set. */
|
|
217
|
+
type ResolveJudgment<A extends InheritedAsk> = Extract<InheritedClassifierJudgment<A>, {
|
|
218
|
+
kind: "resolve";
|
|
219
|
+
}>;
|
|
220
|
+
export declare function inheritedFallbackJudgment<A extends InheritedAsk>(ask: A, fallback: UnarmedDenialLimitFallback, latestBlockedAction: string, facts: AskClassFacts): ResolveJudgment<A>;
|
|
221
|
+
/** One resolution of an inherited ask at its frozen approver (the three arms): a no-ruling card its POLICY refused counts
|
|
222
|
+
* ({@link settleNoRulingAsk}); at the bound the call is resolved again there as the fallback. Returns the judgment it answered. */
|
|
223
|
+
export declare function resolveInheritedAsk<A extends InheritedAsk, R extends Pick<ResolvedAsk, "action" | "settlement">>(judged: ResolveJudgment<A>, resolveWith: (j: ResolveJudgment<A>) => Promise<R>, tracker: AutoModeDenialTracker | undefined, toolName: string, facts: AskClassFacts): Promise<{
|
|
224
|
+
resolved: R;
|
|
225
|
+
judged: ResolveJudgment<A>;
|
|
226
|
+
}>;
|
|
211
227
|
/** The resolver's answer as the settlement reads it. */
|
|
212
228
|
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
|
|
213
229
|
/** A deny nobody JUDGED, at a wrapper arm — read off the one table over the settlement words
|
|
@@ -258,3 +274,4 @@ export declare function lateAskSettlementObserver(args: {
|
|
|
258
274
|
kind: "error";
|
|
259
275
|
error: unknown;
|
|
260
276
|
}) => void;
|
|
277
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { markTightenMessage } from "../safety-axis-vocab.js";
|
|
2
|
-
import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
2
|
+
import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, isAutoModeFallbackCause, rungDidNotRule, unarmedWindow } from "../auto-mode.js";
|
|
3
3
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
4
|
-
import {
|
|
4
|
+
import { classifierNoRulingArm, settleNoRulingAsk } from "../classifier-unavailable-arm.js";
|
|
5
5
|
import { attestClassifierDenyCause, attestClassifierRound, carryEngineAttestations } from "../tool-policy.js";
|
|
6
6
|
import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
|
|
7
7
|
import { deliverEngineNotice } from "../types.js";
|
|
@@ -44,7 +44,7 @@ export function createDenialLimitStop(opts) {
|
|
|
44
44
|
if (gateStopRef.terminal !== undefined)
|
|
45
45
|
return;
|
|
46
46
|
const lane = info.settlement === undefined
|
|
47
|
-
? `
|
|
47
|
+
? `nobody in this run can be asked to answer it`
|
|
48
48
|
: `the durable approval it parked was settled with nobody judging it ("${info.settlement}")`;
|
|
49
49
|
const message = `too many classifier denials in headless mode — ${denialLimitSentence(info.fallback)} The run was stopped: ` +
|
|
50
50
|
`the auto-mode classifier's denial limit falls back to a person, and ${lane} ` +
|
|
@@ -61,6 +61,7 @@ export function createDenialLimitStop(opts) {
|
|
|
61
61
|
return { gateStopRef, stopForDenialLimit };
|
|
62
62
|
}
|
|
63
63
|
export const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
|
|
64
|
+
const noRulingCarry = (why) => (isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {});
|
|
64
65
|
function fallbackCarry(fallback, liveApprover, tracker) {
|
|
65
66
|
if (fallback === undefined)
|
|
66
67
|
return {};
|
|
@@ -71,6 +72,7 @@ export function gateAskCarry(decision, liveApprover, tracker) {
|
|
|
71
72
|
...(decision.origin !== undefined ? { origin: decision.origin } : {}),
|
|
72
73
|
...(decision.askClass !== undefined && decision.classes !== undefined ? { askClass: decision.askClass, classes: decision.classes } : {}),
|
|
73
74
|
...fallbackCarry(decision.denialLimitFallback, liveApprover, tracker),
|
|
75
|
+
...noRulingCarry(decision.classifierNoRuling),
|
|
74
76
|
};
|
|
75
77
|
}
|
|
76
78
|
export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
@@ -78,6 +80,7 @@ export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
|
78
80
|
origin: judged.origin,
|
|
79
81
|
...askClassMembersOf(judged.classification),
|
|
80
82
|
...fallbackCarry(judged.fallback, liveApprover, tracker),
|
|
83
|
+
...noRulingCarry(judged.ask.classifierNoRuling),
|
|
81
84
|
};
|
|
82
85
|
}
|
|
83
86
|
export const inheritedAskFallbackMessage = (toolName) => `approval required for "${toolName}" (inherited parent policy)`;
|
|
@@ -105,12 +108,12 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
105
108
|
autoMode.denialTracking?.recordAllow();
|
|
106
109
|
return { kind: "allow", result: attributed({ action: "allow", decisionReason: "classifier", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
|
|
107
110
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
const noRuling = rungDidNotRule(verdict);
|
|
112
|
+
const arm = noRuling !== undefined ? classifierNoRulingArm({ ask, req, cause: noRuling, seat: opts.unavailableSeat, aborted: opts.signal.aborted }) : undefined;
|
|
113
|
+
if (arm?.kind === "asked")
|
|
114
|
+
return { kind: "resolve", ask: arm.ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification: classifyAsk(arm.ask, facts), noRulingHere: true };
|
|
115
|
+
if (arm?.kind === "denied")
|
|
112
116
|
return { kind: "deny", result: attributed(arm.deny) };
|
|
113
|
-
}
|
|
114
117
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
115
118
|
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
116
119
|
const tracked = autoMode.denialTracking?.recordBlock();
|
|
@@ -122,10 +125,21 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
122
125
|
};
|
|
123
126
|
return { kind: "deny", result: attributed(verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied) };
|
|
124
127
|
}
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
return inheritedFallbackJudgment(ask, tracked.fallback, reason || category || req.toolName, facts);
|
|
129
|
+
}
|
|
130
|
+
export function inheritedFallbackJudgment(ask, fallback, latestBlockedAction, facts) {
|
|
131
|
+
const { classifierNoRuling: _why, ...standing } = ask;
|
|
132
|
+
const minted = carryEngineAttestations(ask, { ...standing, message: denialLimitFallbackMessage(fallback, latestBlockedAction), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback });
|
|
127
133
|
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, facts), classification: classifyAsk(minted, facts) };
|
|
128
134
|
}
|
|
135
|
+
export async function resolveInheritedAsk(judged, resolveWith, tracker, toolName, facts) {
|
|
136
|
+
const resolved = await resolveWith(judged);
|
|
137
|
+
const refallback = settleNoRulingAsk({ ask: judged.ask, mintedHere: judged.noRulingHere === true, resolved, tracker });
|
|
138
|
+
if (refallback === undefined)
|
|
139
|
+
return { resolved, judged };
|
|
140
|
+
const again = inheritedFallbackJudgment(judged.ask, refallback, toolName, facts);
|
|
141
|
+
return { resolved: await resolveWith(again), judged: again };
|
|
142
|
+
}
|
|
129
143
|
export function headlessDenyAtFold(r) {
|
|
130
144
|
return r.settlement !== undefined && SETTLEMENT_NOBODY_JUDGED[r.settlement.kind];
|
|
131
145
|
}
|
|
@@ -139,7 +139,8 @@ export async function exitGate(pass) {
|
|
|
139
139
|
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 } } : {}) });
|
|
140
140
|
const foldCause = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName });
|
|
141
141
|
const unresolvable = foldCause ?? unresolvableCauseOfSettlement(gate);
|
|
142
|
-
const
|
|
142
|
+
const tableSeat = unresolvable !== undefined ? askUnresolvableRemedySeat(unresolvable) : undefined;
|
|
143
|
+
const remedySeat = tableSeat === "absent" && input.askSeatForm === "declared_none" && pass.askClassification?.classes.has("content_question") !== true ? "declared_none" : tableSeat;
|
|
143
144
|
const remedy = remedySeat !== undefined ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: foldCause !== undefined ? ANCESTOR_UNRESOLVED_CLASSIFICATION : pass.askClassification, approverSeat: remedySeat, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
|
|
144
145
|
const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
|
|
145
146
|
if (unresolvable !== undefined) {
|
|
@@ -32,8 +32,8 @@ import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage }
|
|
|
32
32
|
import { type ToolCallLike } from "../tool-registry.js";
|
|
33
33
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
34
34
|
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
35
|
-
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
|
|
36
|
-
*
|
|
35
|
+
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for it and the
|
|
36
|
+
* name tools). The PATH and NAME grammars' tools are derived (`ruleToolGrammarOf`). */
|
|
37
37
|
export declare const PERSISTED_RULE_TOOL = "Bash";
|
|
38
38
|
/**
|
|
39
39
|
* The evidence an INHERITED-lane ask carries.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
|
-
import { adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
|
+
import { adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, 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
6
|
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
@@ -155,6 +155,8 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
155
155
|
const liveCwd = cfg.liveCwd();
|
|
156
156
|
if (grammar === "path")
|
|
157
157
|
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
|
|
158
|
+
if (grammar === "name")
|
|
159
|
+
return persistedRuleAnswerOf(adjudicatePersistedNameRules(table, { tool: req.toolName, name: nameRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId }));
|
|
158
160
|
const command = req.args?.command;
|
|
159
161
|
if (typeof command !== "string")
|
|
160
162
|
return undefined;
|
|
@@ -172,7 +174,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
172
174
|
export function createRuleOffersOf(cfg) {
|
|
173
175
|
return (call, args, ask) => {
|
|
174
176
|
const toolName = wireOf("offer.laneTool", call);
|
|
175
|
-
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
177
|
+
if (!cfg.laneArmed || (toolName !== PERSISTED_RULE_TOOL && ruleToolGrammarOf(toolName) !== "name"))
|
|
176
178
|
return {};
|
|
177
179
|
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
178
180
|
return {};
|
|
@@ -184,6 +186,10 @@ export function createRuleOffersOf(cfg) {
|
|
|
184
186
|
}).word;
|
|
185
187
|
if (closedDoor !== undefined)
|
|
186
188
|
return { ruleOffersAbsence: closedDoor };
|
|
189
|
+
if (ruleToolGrammarOf(toolName) === "name") {
|
|
190
|
+
const offer = suggestRuleForName(toolName, nameRuleSubjectOf(toolName, args) ?? "");
|
|
191
|
+
return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
|
|
192
|
+
}
|
|
187
193
|
const command = args?.command;
|
|
188
194
|
if (typeof command !== "string")
|
|
189
195
|
return { ruleOffersAbsence: "lane_cannot_speak" };
|
|
@@ -6,6 +6,7 @@ import { type OnAsk, type ToolCallRequest, type ToolPolicy } from "../tool-polic
|
|
|
6
6
|
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
7
7
|
import type { CwdRef } from "../../tools/fs/fs-shared.js";
|
|
8
8
|
import type { AskLane, Prepared } from "./contracts.js";
|
|
9
|
+
import type { GrantBindingOf } from "../skill-grant-binding.js";
|
|
9
10
|
import { consumeInheritedAskGrant } from "./inherited-ask-grants.js";
|
|
10
11
|
import { type GrantReadRoot } from "./read-root-grant.js";
|
|
11
12
|
import type { createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
@@ -49,6 +50,8 @@ export interface PrepareAskLaneInput {
|
|
|
49
50
|
now: () => number;
|
|
50
51
|
/** borrowed-readonly — the policy-chain phase's rule-offer factory (the suggestion members the ask mint threads). */
|
|
51
52
|
ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
|
|
53
|
+
/** borrowed-readonly — the policy-chain phase's Skill-card binding factory (`boundGrants` / `boundGrantDigest` on the ask). */
|
|
54
|
+
grantBindingOf: GrantBindingOf;
|
|
52
55
|
/** borrowed-readonly — the policy-chain phase's Runner-filled source identity (one factory, every ask mint site). */
|
|
53
56
|
askSourceIdentity: () => {
|
|
54
57
|
principal?: string;
|
|
@@ -59,7 +59,7 @@ function resolveApprovalPreview(tools, toolName, args) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
export function prepareAskLane(input) {
|
|
62
|
-
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
62
|
+
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
63
63
|
if (!gateMachineryActive)
|
|
64
64
|
return { askLane: undefined };
|
|
65
65
|
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
@@ -98,6 +98,7 @@ export function prepareAskLane(input) {
|
|
|
98
98
|
return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
|
|
99
99
|
})(),
|
|
100
100
|
...ruleOffersOf(req, req.args, decision.action === "ask" ? decision : undefined),
|
|
101
|
+
...grantBindingOf(req, req.args),
|
|
101
102
|
...cardDisclosureOf(decision, `approval required for "${req.toolName}"`),
|
|
102
103
|
...askSourceIdentity(),
|
|
103
104
|
...riskAxesOf(req),
|
|
@@ -30,7 +30,6 @@ import { type CheckpointSeat } from "../checkpoint-seat.js";
|
|
|
30
30
|
import { type ComplianceCapability } from "../compliance.js";
|
|
31
31
|
import type { LockedPreflight } from "../locked-config.js";
|
|
32
32
|
import type { StoredSession } from "../session.js";
|
|
33
|
-
import type { OnAsk } from "../tool-policy.js";
|
|
34
33
|
import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
|
|
35
34
|
import type { AutoModeArmFact } from "../wiring-manifest.js";
|
|
36
35
|
import type { AssertAllKeysHandled } from "../ask-origin.js";
|
|
@@ -88,8 +87,8 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
88
87
|
harnessRef: {
|
|
89
88
|
current?: AgentHarness;
|
|
90
89
|
};
|
|
91
|
-
/** borrowed-readonly — the
|
|
92
|
-
|
|
90
|
+
/** borrowed-readonly — the run's approval seat as the config doors froze it (the Workflow mount's parentOnAsk / parentApprovalReach). */
|
|
91
|
+
askSeat: import("../wiring-manifest.js").AskSeat;
|
|
93
92
|
/** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat;
|
|
94
93
|
/** borrowed-readonly — the ONE read-face carrier expression (the Workflow mount's parentReadFace getter calls it). */
|
|
95
94
|
carrierReadFace: () => ContainmentFace | undefined;
|
|
@@ -114,7 +114,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
|
|
|
114
114
|
return { armed: true, reason: "armed" };
|
|
115
115
|
}
|
|
116
116
|
export async function prepareCapsAndWorkflow(input) {
|
|
117
|
-
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef,
|
|
117
|
+
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, askSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
|
|
118
118
|
const roster = new RosterBuilder(toolEffects);
|
|
119
119
|
for (const t of spec.tools ?? []) {
|
|
120
120
|
const mounted = mountCallerTool(t);
|
|
@@ -326,7 +326,8 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
326
326
|
return model === undefined ? undefined : { model, ...(typeof ref === "string" ? { modelRef: ref } : {}) };
|
|
327
327
|
},
|
|
328
328
|
...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
|
|
329
|
-
...(
|
|
329
|
+
...(askSeat.value !== undefined ? { parentOnAsk: askSeat.value } : {}),
|
|
330
|
+
...(askSeat.reach !== undefined ? { parentApprovalReach: askSeat.reach } : {}),
|
|
330
331
|
...(spec.durableApproval !== undefined ? { parentDurableApproval: { ...spec.durableApproval } } : {}),
|
|
331
332
|
...(internals?.workflowParkedResume !== undefined
|
|
332
333
|
? { parkedResume: (resumeFromRunId) => {
|
|
@@ -29,6 +29,7 @@ import { type ResolvedSkillListingBudget } from "../skill-listing-budget.js";
|
|
|
29
29
|
import type { SessionStore } from "../session.js";
|
|
30
30
|
import type { RunnerDeps, TaskLimits, TaskSpec } from "../types.js";
|
|
31
31
|
import { type UsageWindow } from "../usage-window-store.js";
|
|
32
|
+
import { type AskSeat } from "../wiring-manifest.js";
|
|
32
33
|
import type { PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
|
|
33
34
|
export declare function limitConfigError(code: string, message: string): Error & {
|
|
34
35
|
code?: string;
|
|
@@ -147,6 +148,8 @@ export interface PrepareConfigDoorsResult {
|
|
|
147
148
|
/** owned — the resolved interaction posture (spec > spawning run > deployment); the
|
|
148
149
|
* AskUserQuestion mount and the child-ctx injection read this same value. */
|
|
149
150
|
resolvedInteractionPosture: "interactive" | "headless" | undefined;
|
|
151
|
+
/** owned, frozen — the run's approval seat ({@link resolveAskSeat}), the one read of the seats; later phases read only this. */
|
|
152
|
+
askSeat: AskSeat;
|
|
150
153
|
/** owned — the validated/resolved microCompact knob ({@link resolveMicroCompactKnob}
|
|
151
154
|
* for the door + placement reasoning). The per-run state builder consumes exactly these values —
|
|
152
155
|
* the deps bag is never re-read after this door. */
|