@sema-agent/core 7.42.1 → 7.44.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 +173 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +3 -1
- package/dist/agents/subagent.js +5 -3
- package/dist/agents/teacher.js +1 -1
- package/dist/agents/verify.js +2 -0
- package/dist/brain/errors.d.ts +23 -2
- package/dist/brain/errors.js +26 -2
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +10 -9
- 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 +44 -20
- package/dist/core/checkpoint-store.js +10 -16
- 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/engine-notice.d.ts +16 -14
- package/dist/core/engine-notice.js +4 -3
- package/dist/core/gate-lanes.js +37 -29
- package/dist/core/governance-codes.d.ts +4 -3
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +6 -1
- package/dist/core/hooks.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +7 -14
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +5 -7
- package/dist/core/memory-engine/layout.d.ts +37 -4
- package/dist/core/memory-engine/layout.js +34 -5
- 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/contracts.d.ts +1 -16
- 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/runtask.js +1 -5
- package/dist/core/runner/steer-admission.d.ts +10 -11
- package/dist/core/runner/steer-admission.js +6 -8
- package/dist/core/runner/stream-steer-verb.d.ts +5 -4
- package/dist/core/runner/stream-steer-verb.js +6 -8
- 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/steer-replay.d.ts +46 -0
- package/dist/core/steer-replay.js +22 -0
- package/dist/core/task-event.d.ts +3 -2
- package/dist/core/task-spec.d.ts +14 -1
- package/dist/core/task-stream.d.ts +12 -6
- package/dist/core/tighten-task-spec.d.ts +2 -0
- package/dist/core/tighten-task-spec.js +3 -0
- package/dist/core/tool-errors.d.ts +2 -2
- 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/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +56 -14
- package/dist/core/wiring-manifest.js +29 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -4
- 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/dist/orchestration/workflow-types.d.ts +5 -0
- package/dist/orchestration/workflow.d.ts +3 -2
- package/dist/orchestration/workflow.js +14 -6
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -5
- package/wire-changes/core@7.43.0.json +985 -0
- package/wire-changes/core@7.44.0.json +469 -0
|
@@ -156,14 +156,27 @@ export declare function callPathFaceOf(call: ToolCallLike): DenyFace | undefined
|
|
|
156
156
|
/** Does some {@link PATH_RULE_FACES} ask column hold `face` — may a face-keyed ask row speak for a call on it, whatever
|
|
157
157
|
* the call's tool is called (a caller's tool declaring a read path target is on the read face too)? */
|
|
158
158
|
export declare function faceAskSpeaksFor(face: DenyFace | undefined): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* The tools whose rule content NAMES THE VALUE of one parameter instead of a command or a path — the reference's `Skill(<name>)`
|
|
161
|
+
* form, one row per tool: `param` is the argument the rule's content is read against, `params` every scalar argument the tool
|
|
162
|
+
* carries (the parameter spelling `Tool(<param>:<pattern>)` those names used to reach is retired for such a tool, see
|
|
163
|
+
* {@link parseNameRuleContent}). The tool's real schema keys are pinned equal to `params` (test/backlog1318).
|
|
164
|
+
*/
|
|
165
|
+
export declare const NAME_RULE_FACES: Readonly<Record<string, {
|
|
166
|
+
readonly param: string;
|
|
167
|
+
readonly params: readonly string[];
|
|
168
|
+
}>>;
|
|
159
169
|
/**
|
|
160
170
|
* Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the shell tool
|
|
161
171
|
* takes the COMMAND grammar; every tool the catalog declares a path target for (Read / Edit / Write /
|
|
162
|
-
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar
|
|
163
|
-
* content this model can judge and is refused (`unsupported.tool`). The tool set therefore
|
|
164
|
-
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
172
|
+
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar.
|
|
173
|
+
* A rule on any other tool has no content this model can judge and is refused (`unsupported.tool`). The tool set therefore
|
|
174
|
+
* moves with the catalog's declarations, exactly as the protective fences' covered set does.
|
|
165
175
|
*/
|
|
166
|
-
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
176
|
+
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | "name" | undefined;
|
|
177
|
+
/** The value a name-grammar tool's call carries in its name argument — the string every NAME rule is read against, the one a
|
|
178
|
+
* `Skill` call is resolved by — or undefined (not a name-grammar tool; no such argument; not a string). */
|
|
179
|
+
export declare function nameRuleSubjectOf(toolName: string, args: unknown): string | undefined;
|
|
167
180
|
/** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
|
|
168
181
|
* name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
169
182
|
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
@@ -191,7 +204,7 @@ export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").Too
|
|
|
191
204
|
* otherwise the working directory). Its predicate is {@link pathRuleReaches}. Not an allow form: a
|
|
192
205
|
* standing approval by path pattern would be a wider grant than any card ever shows a person.
|
|
193
206
|
*/
|
|
194
|
-
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path";
|
|
207
|
+
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path" | "name" | "name_prefix";
|
|
195
208
|
/**
|
|
196
209
|
* Where a rule applies — design/382 §4.1, the THREE consent dimensions.
|
|
197
210
|
*
|
|
@@ -525,6 +538,9 @@ export declare function parseRuleText(text: string, behavior: RuleBehavior): {
|
|
|
525
538
|
* `subpath` the command is the lexical-normal absolute directory (one leading slash), re-spelled in the
|
|
526
539
|
* double-slash directory form; for `path` it is the pattern as spelled. */
|
|
527
540
|
export declare function formatRuleText(command: string, match: PersistedRuleMatch, tool: PersistedRuleTool): string;
|
|
541
|
+
/** Does a name rule reach this name? `name`: equal; `name_prefix`: begins with it (no boundary — the reference's `startsWith`).
|
|
542
|
+
* A rule of any other family reaches no name. */
|
|
543
|
+
export declare function nameRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, name: string): boolean;
|
|
528
544
|
/**
|
|
529
545
|
* The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
|
|
530
546
|
* environment and its task), never of the rule:
|
|
@@ -675,8 +691,8 @@ export type ProgramRunReachOutcome = {
|
|
|
675
691
|
* other segment already reached.
|
|
676
692
|
*
|
|
677
693
|
* A rule body the lexer cannot read (foreign store bytes; every entrance validates against exactly
|
|
678
|
-
* this) reaches nothing. A
|
|
679
|
-
* {@link directoryRuleAdmits} and {@link
|
|
694
|
+
* this) reaches nothing. A rule of another family (path: `subpath` / `path`; name: `name` / `name_prefix`) reaches no
|
|
695
|
+
* command: its predicates are {@link directoryRuleAdmits}, {@link pathRuleReachOf} and {@link nameRuleAdmits}.
|
|
680
696
|
*/
|
|
681
697
|
export declare function programRunReachOf(rule: Pick<PersistedRule, "match" | "command">, command: string): ProgramRunReachOutcome;
|
|
682
698
|
/** The boolean face of {@link programRunReachOf}: `reached` only. The shadow view and the pins that
|
|
@@ -926,6 +942,30 @@ export declare function adjudicatePersistedPathRules(rules: readonly PersistedRu
|
|
|
926
942
|
readonly tighten?: PathTargetReading;
|
|
927
943
|
readonly allow?: PathTargetReading;
|
|
928
944
|
}, bases: PathRuleBases): PersistedRuleVerdict | undefined;
|
|
945
|
+
/**
|
|
946
|
+
* The name-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules of one name tool
|
|
947
|
+
* ({@link NAME_RULE_FACES}), judged on the one string the call names (`nameRuleSubjectOf`). A rule of the tool speaks for the call
|
|
948
|
+
* when it is live, of this behavior, in scope ({@link eligiblePersisted}, the one predicate) and {@link nameRuleAdmits} the name; the
|
|
949
|
+
* first behavior in {@link RULE_BEHAVIORS_BY_PRECEDENCE} whose rules speak decides, the first table-order rule of it is the answer.
|
|
950
|
+
* Every behavior is judged with the one predicate — a name has no "program run" the tightening direction could read wider — and
|
|
951
|
+
* there is nothing unreadable: a name is a string.
|
|
952
|
+
*/
|
|
953
|
+
export declare function adjudicatePersistedNameRules(rules: readonly PersistedRule[], call: {
|
|
954
|
+
tool: string;
|
|
955
|
+
name: string | undefined;
|
|
956
|
+
cwd: string | undefined;
|
|
957
|
+
sessionId?: string;
|
|
958
|
+
}): PersistedRuleVerdict | undefined;
|
|
959
|
+
/**
|
|
960
|
+
* The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
|
|
961
|
+
* "don't ask again for <skill>" row), or undefined when no rule spells exactly that name (the round-trip law: the canonical text,
|
|
962
|
+
* parsed back as an allow, must be the exact name rule over the very name — a leading slash, an interior star or an unreadable
|
|
963
|
+
* spelling offers nothing rather than a rule that would not clear the ask it was minted on). The reference's second row, the first
|
|
964
|
+
* word as a prefix, is offered only for a name containing a space; it is not minted here (it would widen past the name shown).
|
|
965
|
+
*/
|
|
966
|
+
export declare function suggestRuleForName(tool: string, name: string): Extract<RuleOffer, {
|
|
967
|
+
kind: "single";
|
|
968
|
+
}> | undefined;
|
|
929
969
|
/**
|
|
930
970
|
* design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
|
|
931
971
|
* already admits, judged with the SAME split and the same eligibility predicates as the gate's
|
|
@@ -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."
|
|
@@ -33,7 +33,7 @@ import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
|
33
33
|
import type { AutoModeDecider, AutoModeDenialTracker, DenialLimitFallbackFace } from "../auto-mode.js";
|
|
34
34
|
import type { SettlementKind } from "../gate-outcome.js";
|
|
35
35
|
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
|
|
36
|
-
import type {
|
|
36
|
+
import type { Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
|
|
37
37
|
import type { ExecutionOutcomeRecordWord } from "../checkpoint-execution-record.js";
|
|
38
38
|
import type { ClearedProjectionLedger, MicroCompactMachine, OccurrenceIndex } from "../context-edit.js";
|
|
39
39
|
import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, PostToolBatchCall, PostToolBatchResult, ToolGateInput } from "../hooks.js";
|
|
@@ -2494,21 +2494,6 @@ export interface RunState {
|
|
|
2494
2494
|
attachmentsInjected: number;
|
|
2495
2495
|
};
|
|
2496
2496
|
}
|
|
2497
|
-
/** One accepted live steer's REPLAY identity, the twin of the parked
|
|
2498
|
-
* queue's `samePendingSteerPayload`. `payload` is the exact string handed to the harness, so the text,
|
|
2499
|
-
* the speaker envelope and the trust framing all fold into it; `trusted` is kept SEPARATELY from those
|
|
2500
|
-
* framed bytes so a text that spells its own `<system-reminder>` wrapper can never read as the trusted
|
|
2501
|
-
* arm's byte-equal twin. `actor` carries the attribution the ledger frame reports. */
|
|
2502
|
-
export interface AcceptedSteerInput {
|
|
2503
|
-
payload: string;
|
|
2504
|
-
trusted: boolean;
|
|
2505
|
-
/** The NORMALIZED priority the call delivered under (absent input ⇒ "next"),
|
|
2506
|
-
* the replay identity's sixth axis: same id + same text at a DIFFERENT tier is not a replay —
|
|
2507
|
-
* a `next` input re-sent as `now` answering "idempotent success" without the interrupt would be
|
|
2508
|
-
* a disposition lie, so it refuses `duplicate_input_id` like any other identity mismatch. */
|
|
2509
|
-
priority: SystemInjectionPriority;
|
|
2510
|
-
actor?: ActorAssertion;
|
|
2511
|
-
}
|
|
2512
2497
|
/**
|
|
2513
2498
|
* Config re-supplied to {@link Runner.resume}. A suspended task's tools / model / policy /
|
|
2514
2499
|
* hooks cannot be reconstructed from a checkpoint token (the session stores neither tool implementations
|
|
@@ -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.
|