@sema-agent/core 7.42.0 → 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 +124 -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 +19 -8
- package/dist/core/checkpoint-store.js +8 -4
- 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/steer-admission.js +2 -1
- package/dist/core/runner/stream-steer-verb.js +2 -2
- 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-notification.d.ts +6 -0
- package/dist/core/task-notification.js +3 -0
- 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 +4 -3
- package/dist/index.js +3 -3
- 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 +13 -1
- package/wire-changes/core@7.42.1.json +56 -0
- package/wire-changes/core@7.43.0.json +985 -0
package/dist/core/allow-entry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { heldBy } from "./tool-name-readings.js";
|
|
2
2
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
|
|
3
|
-
import {
|
|
3
|
+
import { judgeRuleToolName, splitNamespacedName } from "./tool-name-aliases.js";
|
|
4
|
+
import { catalogToolNames } from "./tool-registry.js";
|
|
4
5
|
import { COARSE_SHELL_TOOLS } from "./tool-policy.js";
|
|
5
6
|
import { ALLOW_RULE_MEMBERS } from "./allow-rule-shadow.js";
|
|
6
7
|
import { COMMAND_RULE_TOOL, READ_RULE_TOOL, findAdmittingRule, laneToolNamesOf, parseRuleText, programRunReachOf, ruleToolGrammarOf } from "./permission-rule-model.js";
|
|
@@ -9,66 +10,95 @@ import { splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier
|
|
|
9
10
|
import { mandateDisclosureOf } from "./ask-origin.js";
|
|
10
11
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
11
12
|
import { deliverEngineNotice } from "./engine-notice.js";
|
|
12
|
-
export const ALLOW_ENTRY_INERT_REASONS = Object.freeze(["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar"]);
|
|
13
|
+
export const ALLOW_ENTRY_INERT_REASONS = Object.freeze(["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar", "tool_name", "near_miss"]);
|
|
13
14
|
function ruleRowOf(parsed) {
|
|
14
15
|
return { ...parsed, scope: { kind: "global" }, adds: [{ dot: { actor: "list", counter: 1 }, origin: "user", createdAt: "" }] };
|
|
15
16
|
}
|
|
16
17
|
const TOOL_NAME_SPELLING = /^[^\s()]+$/;
|
|
17
|
-
|
|
18
|
+
function mountedSpellingOf(name, roster) {
|
|
19
|
+
const folded = name.toLowerCase();
|
|
20
|
+
for (const mounted of roster)
|
|
21
|
+
if (mounted.toLowerCase() === folded)
|
|
22
|
+
return mounted;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function toolNameReading(name, roster) {
|
|
26
|
+
const verdict = judgeRuleToolName(name, roster);
|
|
27
|
+
if (verdict.kind !== "current") {
|
|
28
|
+
const currentName = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
|
|
29
|
+
return { inert: "tool_name", detail: { verdict: verdict.kind, ...(currentName !== undefined ? { currentName } : {}) } };
|
|
30
|
+
}
|
|
31
|
+
if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const mounted = mountedSpellingOf(name, roster);
|
|
34
|
+
return mounted === undefined ? undefined : { inert: "near_miss", detail: { currentName: mounted } };
|
|
35
|
+
}
|
|
36
|
+
function readAllowEntry(raw, behavior, roster) {
|
|
37
|
+
const inert = (reason) => ({ inert: reason });
|
|
38
|
+
const row = (compiled) => ({ compiled });
|
|
18
39
|
if (raw !== raw.trim())
|
|
19
|
-
return "grammar";
|
|
40
|
+
return inert("grammar");
|
|
20
41
|
const parsed = parsePermissionRule(raw);
|
|
21
42
|
const name = parsed.toolName;
|
|
22
43
|
if (!TOOL_NAME_SPELLING.test(name))
|
|
23
|
-
return "grammar";
|
|
44
|
+
return inert("grammar");
|
|
24
45
|
if (name === "*")
|
|
25
|
-
return "wildcard_all";
|
|
46
|
+
return inert("wildcard_all");
|
|
47
|
+
if (roster !== undefined) {
|
|
48
|
+
const stale = toolNameReading(name, roster);
|
|
49
|
+
if (stale !== undefined)
|
|
50
|
+
return stale;
|
|
51
|
+
}
|
|
26
52
|
if (parsed.ruleContent === undefined) {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
if (
|
|
30
|
-
return "namespace_wildcard";
|
|
53
|
+
const split = splitNamespacedName(name);
|
|
54
|
+
if (split !== undefined) {
|
|
55
|
+
if (split.peer.includes("*"))
|
|
56
|
+
return inert("namespace_wildcard");
|
|
31
57
|
if (isNamespacedCoveringRuleName(name))
|
|
32
|
-
return { kind: "namespace", pattern: name };
|
|
33
|
-
return { kind: "tool", name };
|
|
58
|
+
return row({ kind: "namespace", pattern: name });
|
|
59
|
+
return row({ kind: "tool", name });
|
|
34
60
|
}
|
|
35
|
-
return COARSE_SHELL_TOOLS.includes(name) ? { kind: "shell", name } : { kind: "tool", name };
|
|
61
|
+
return row(COARSE_SHELL_TOOLS.includes(name) ? { kind: "shell", name } : { kind: "tool", name });
|
|
36
62
|
}
|
|
37
63
|
if (name === COMMAND_RULE_TOOL) {
|
|
38
64
|
const allowRead = parseRuleText(raw, behavior);
|
|
39
65
|
if ("rule" in allowRead)
|
|
40
|
-
return { kind: "command", rule: ruleRowOf(allowRead.rule) };
|
|
66
|
+
return row({ kind: "command", rule: ruleRowOf(allowRead.rule) });
|
|
41
67
|
if (behavior === "allow" && allowRead.reject.code === "invalid.bare_interpreter_prefix") {
|
|
42
68
|
const wide = parseRuleText(raw, "deny");
|
|
43
69
|
if ("rule" in wide)
|
|
44
|
-
return { kind: "command", rule: ruleRowOf({ ...wide.rule, behavior: "allow" }) };
|
|
70
|
+
return row({ kind: "command", rule: ruleRowOf({ ...wide.rule, behavior: "allow" }) });
|
|
45
71
|
}
|
|
46
|
-
return "grammar";
|
|
72
|
+
return inert("grammar");
|
|
47
73
|
}
|
|
48
74
|
if (ruleToolGrammarOf(name) === "path") {
|
|
49
75
|
const read = parseRuleText(raw, behavior);
|
|
50
76
|
if ("rule" in read) {
|
|
51
77
|
if (read.rule.match === "subpath" && read.rule.tool === READ_RULE_TOOL && behavior === "allow")
|
|
52
|
-
return { kind: "read_dir", rule: ruleRowOf(read.rule) };
|
|
78
|
+
return row({ kind: "read_dir", rule: ruleRowOf(read.rule) });
|
|
53
79
|
if (behavior === "deny")
|
|
54
|
-
return { kind: "path", rule: ruleRowOf(read.rule) };
|
|
80
|
+
return row({ kind: "path", rule: ruleRowOf(read.rule) });
|
|
55
81
|
}
|
|
56
|
-
return "path_pattern";
|
|
82
|
+
return inert("path_pattern");
|
|
57
83
|
}
|
|
58
84
|
if (/^domain:/.test(parsed.ruleContent))
|
|
59
|
-
return "domain";
|
|
60
|
-
return "content_unsupported";
|
|
85
|
+
return inert("domain");
|
|
86
|
+
return inert("content_unsupported");
|
|
61
87
|
}
|
|
62
|
-
export function
|
|
88
|
+
export function compileAllowEntry(raw, behavior, roster) {
|
|
89
|
+
const read = readAllowEntry(raw, behavior, roster);
|
|
90
|
+
return "compiled" in read ? read.compiled : read.inert;
|
|
91
|
+
}
|
|
92
|
+
export function compileAllowList(entries, behavior, roster) {
|
|
63
93
|
const voided = behavior === "allow" && entries.some((e) => compileAllowEntry(e, behavior) === "wildcard_all");
|
|
64
94
|
const rows = [];
|
|
65
95
|
const inert = [];
|
|
66
96
|
for (const entry of entries) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
97
|
+
const read = voided ? { inert: "wildcard_all" } : readAllowEntry(entry, behavior, roster);
|
|
98
|
+
if ("compiled" in read)
|
|
99
|
+
rows.push({ entry, compiled: read.compiled });
|
|
70
100
|
else
|
|
71
|
-
|
|
101
|
+
inert.push({ entry, reason: read.inert, ...read.detail });
|
|
72
102
|
}
|
|
73
103
|
return { rows, inert };
|
|
74
104
|
}
|
|
@@ -179,11 +209,15 @@ export function allowingRows(rows, req, mode) {
|
|
|
179
209
|
}
|
|
180
210
|
export class AllowEntryTable {
|
|
181
211
|
held = [];
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
212
|
+
heldEntries = new Set();
|
|
213
|
+
seed(entries, roster) {
|
|
214
|
+
const { rows, inert } = compileAllowList(entries, "allow", roster);
|
|
215
|
+
for (const r of rows) {
|
|
216
|
+
if (this.heldEntries.has(r.entry))
|
|
217
|
+
continue;
|
|
218
|
+
this.heldEntries.add(r.entry);
|
|
219
|
+
this.held.push({ behavior: "allow", entry: r.entry, compiled: r.compiled });
|
|
220
|
+
}
|
|
187
221
|
return inert;
|
|
188
222
|
}
|
|
189
223
|
entries() {
|
|
@@ -198,11 +232,15 @@ export class AllowEntryTable {
|
|
|
198
232
|
};
|
|
199
233
|
}
|
|
200
234
|
}
|
|
201
|
-
export function seedAllowEntryTables(tables, lists) {
|
|
235
|
+
export function seedAllowEntryTables(tables, lists, roster) {
|
|
202
236
|
tables.request.seed(lists.inherited ?? []);
|
|
203
|
-
const requestInert = tables.request.seed(lists.request ?? []);
|
|
204
|
-
tables.deployment.seed(lists.deployment ?? []);
|
|
205
|
-
return [
|
|
237
|
+
const requestInert = tables.request.seed(lists.request ?? [], roster);
|
|
238
|
+
const deploymentInert = tables.deployment.seed(lists.deployment ?? [], roster).filter((e) => e.reason === "tool_name" || e.reason === "near_miss");
|
|
239
|
+
return [
|
|
240
|
+
...lists.skillInert.map((e) => ({ source: "skill", ...e })),
|
|
241
|
+
...requestInert.map((e) => ({ source: "request", ...e })),
|
|
242
|
+
...deploymentInert.map((e) => ({ source: "deployment", ...e })),
|
|
243
|
+
];
|
|
206
244
|
}
|
|
207
245
|
export function withDeploymentAllowedTools(deps) {
|
|
208
246
|
if (deps.allowedTools === undefined)
|
|
@@ -276,6 +314,7 @@ const SHADOW_REASON = {
|
|
|
276
314
|
rule_unreadable: "this call could not be read against a deny or ask rule, so whether one forbids it is unknown",
|
|
277
315
|
ask_rule: mandateDisclosureOf(undefined),
|
|
278
316
|
denial_limit_fallback: "the auto-mode classifier refused too many calls in a row, so a person decides",
|
|
317
|
+
classifier_no_ruling: "the auto-mode classifier could not judge this call, so a person decides",
|
|
279
318
|
requires_user_interaction: "this tool's server declares that a call must be put to a person",
|
|
280
319
|
operator_forced: "this call arrived already marked as requiring confirmation (an earlier layer of this run, or the task that delegated it, judged it so) — it is cleared by confirming this call, never by a standing allow rule",
|
|
281
320
|
shell_floor: mandateDisclosureOf("operator_always"),
|
|
@@ -327,14 +366,38 @@ export function cardDisclosureOf(decision, fallbackMessage) {
|
|
|
327
366
|
}
|
|
328
367
|
export const ALLOWED_TOOL_NOT_GRANTED_CODE = "permission.allowed_tool_not_granted";
|
|
329
368
|
const NOTICE_ENTRIES_SHOWN = 5;
|
|
369
|
+
export function inertEntryPhrase(e) {
|
|
370
|
+
const spelling = e.currentName !== undefined ? inlineUntrusted(e.currentName, 120) : undefined;
|
|
371
|
+
const why = (() => {
|
|
372
|
+
if (e.reason === "near_miss")
|
|
373
|
+
return `near_miss: the mounted spelling is "${spelling}"`;
|
|
374
|
+
if (e.reason !== "tool_name" || e.verdict === undefined)
|
|
375
|
+
return e.reason;
|
|
376
|
+
switch (e.verdict) {
|
|
377
|
+
case "retired":
|
|
378
|
+
return `tool_name: a retired tool name${spelling !== undefined ? `, now "${spelling}"` : ""}`;
|
|
379
|
+
case "unprefixed_namespace":
|
|
380
|
+
return 'tool_name: a "__" name with no protocol prefix';
|
|
381
|
+
case "unmintable_segment":
|
|
382
|
+
return `tool_name: a peer segment the engine never mints${spelling !== undefined ? `, write "${spelling}"` : ""}`;
|
|
383
|
+
default: {
|
|
384
|
+
const exhaustive = e.verdict;
|
|
385
|
+
return exhaustive;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
})();
|
|
389
|
+
return `${inlineUntrusted(JSON.stringify(e.entry), 122)} (${why})`;
|
|
390
|
+
}
|
|
330
391
|
export function allowedToolsNotGrantedNotice(inert, sessionId) {
|
|
331
392
|
const where = (e) => (e.source === "skill" ? `skill ${inlineUntrusted(e.skill, 80)} ${e.list}` : e.source === "request" ? "this run's allowedTools" : "the deployment's allowedTools");
|
|
332
|
-
const shown = inert.slice(0, NOTICE_ENTRIES_SHOWN).map((e) => `${where(e)}
|
|
393
|
+
const shown = inert.slice(0, NOTICE_ENTRIES_SHOWN).map((e) => `${where(e)} ${inertEntryPhrase(e)}`);
|
|
333
394
|
const rest = inert.length - shown.length;
|
|
395
|
+
const refusing = inert.some((e) => "list" in e && e.list === "disallowed-tools");
|
|
334
396
|
return {
|
|
335
397
|
code: ALLOWED_TOOL_NOT_GRANTED_CODE,
|
|
336
398
|
message: `${inert.length} allowed-tools entr${inert.length === 1 ? "y grants" : "ies grant"} or refuse${inert.length === 1 ? "s" : ""} nothing — ${shown.join("; ")}${rest > 0 ? `; and ${rest} more` : ""}. ` +
|
|
337
|
-
"A list holding `*` voids the list, `mcp__*` names no server,
|
|
399
|
+
"A list holding `*` voids the list, `mcp__*` names no server, a write-path or domain entry has no allow reading, and a tool name must be spelled as the tool is mounted; name the tools (or `mcp__<server>`) instead." +
|
|
400
|
+
(refusing ? " Such an entry in a disallowed-tools list keeps its Skill from activating: a refusal that reads as nothing is not dropped." : ""),
|
|
338
401
|
detail: { ...(sessionId !== undefined ? { sessionId } : {}), entries: inert.map((e) => ({ ...e })) },
|
|
339
402
|
};
|
|
340
403
|
}
|
|
@@ -445,8 +445,11 @@ export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
|
|
|
445
445
|
* row cell by cell, because the reference implementation judges the declaration ahead of its allow-rule lookup and its bypass release.
|
|
446
446
|
* `unstated` is the mode's answer for a call nobody stated: a tool whose permission seat is `passthrough` (it states no
|
|
447
447
|
* permission of its own — a remote peer's tool) reached the end of the fold with an allow no layer stated.
|
|
448
|
+
* `classifier_no_ruling` is a classifier station's question to a person because the auto-mode classification did not rule
|
|
449
|
+
* (`classifierNoRuling` — it could not run, or replied with no verdict): the denial-limit fallback's row (only a person
|
|
450
|
+
* answers it; the classifier is never asked again).
|
|
448
451
|
*/
|
|
449
|
-
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "requires_user_interaction", "operator_forced", "shell_floor", "write_protection_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
452
|
+
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "write_protection_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
450
453
|
export type AskClass = (typeof ASK_CLASSES)[number];
|
|
451
454
|
/** Membership test for {@link AskClass} — the word crosses the wire on `tool_end.gate.disposition`. */
|
|
452
455
|
export declare function isAskClass(v: unknown): v is AskClass;
|
|
@@ -549,6 +552,13 @@ export declare const MODE_ARBITRATION: {
|
|
|
549
552
|
readonly ruleMayClear: false;
|
|
550
553
|
readonly hookAskFloor: false;
|
|
551
554
|
};
|
|
555
|
+
readonly classifier_no_ruling: {
|
|
556
|
+
readonly bypassImmune: true;
|
|
557
|
+
readonly classifierEligible: false;
|
|
558
|
+
readonly sandboxAdmission: false;
|
|
559
|
+
readonly ruleMayClear: false;
|
|
560
|
+
readonly hookAskFloor: false;
|
|
561
|
+
};
|
|
552
562
|
readonly requires_user_interaction: {
|
|
553
563
|
readonly bypassImmune: true;
|
|
554
564
|
readonly classifierEligible: false;
|
|
@@ -725,9 +735,10 @@ export declare function classifyAsk(d: Extract<PermissionResult, {
|
|
|
725
735
|
* and the ask's prompt as what was requested. The gate appends the caller-side remedy. */
|
|
726
736
|
export declare function headlessDenialMessage(toolName: string, askMessage: string): string;
|
|
727
737
|
/** The state of the approver seat a remedy is written for: `absent` — no approver is wired for the leg; `unavailable` — one is
|
|
728
|
-
* wired and answered that nobody is reachable for this ask
|
|
729
|
-
*
|
|
730
|
-
|
|
738
|
+
* wired and answered that nobody is reachable for this ask; `declared_none` — the run declared that nobody on it can be
|
|
739
|
+
* asked (`approvalReach: "none"`), whatever is wired. Absent in the input reads as `absent` (the posture the function was
|
|
740
|
+
* written for). */
|
|
741
|
+
export type HeadlessApproverSeat = "absent" | "unavailable" | "declared_none";
|
|
731
742
|
/**
|
|
732
743
|
* The ONE sentence a headless auto-deny (an ask that reached its final deny because no approver is wired) adds
|
|
733
744
|
* after its refusal: the caller-side change that would have let this call run. Read off the effective mode, the
|
|
@@ -741,7 +752,7 @@ export type HeadlessApproverSeat = "absent" | "unavailable";
|
|
|
741
752
|
* · any other releasable ask — `bypassPermissions` releases it (disclosed on the call).
|
|
742
753
|
* Every arm also names the approver, in the clause of the seat's own state ({@link HeadlessApproverSeat}): none wired ⇒
|
|
743
754
|
* wire one; one wired that answered `unavailable` for this ask ⇒ make a person reachable through it (a wired approver is
|
|
744
|
-
* never told to be wired). The refusal's tool text and the `delegation.ask_unresolvable` notice both carry this sentence —
|
|
755
|
+
* never told to be wired); the run declared nobody can be asked ⇒ run it where a person can be, or park it. The refusal's tool text and the `delegation.ask_unresolvable` notice both carry this sentence —
|
|
745
756
|
* they call this function, neither spells it.
|
|
746
757
|
*/
|
|
747
758
|
export declare function headlessApprovalRemedy(input: {
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -177,6 +177,7 @@ export const ASK_CLASSES = Object.freeze([
|
|
|
177
177
|
"rule_unreadable",
|
|
178
178
|
"ask_rule",
|
|
179
179
|
"denial_limit_fallback",
|
|
180
|
+
"classifier_no_ruling",
|
|
180
181
|
"requires_user_interaction",
|
|
181
182
|
"operator_forced",
|
|
182
183
|
"shell_floor",
|
|
@@ -206,6 +207,7 @@ export const MODE_ARBITRATION = {
|
|
|
206
207
|
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
207
208
|
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
208
209
|
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
210
|
+
classifier_no_ruling: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
209
211
|
requires_user_interaction: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
210
212
|
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
211
213
|
shell_floor: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
@@ -220,7 +222,7 @@ export const MODE_ARBITRATION = {
|
|
|
220
222
|
unstated: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: true, hookAskFloor: false },
|
|
221
223
|
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
222
224
|
};
|
|
223
|
-
export const RRA_EXPLAINERS = new Set(["unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
225
|
+
export const RRA_EXPLAINERS = new Set(["unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
224
226
|
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
225
227
|
export const MODE_RELEASE_REASONS = Object.freeze(["rule_unreadable"]);
|
|
226
228
|
const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS);
|
|
@@ -253,6 +255,8 @@ export function decisionAskClassesOf(d) {
|
|
|
253
255
|
out.add("ask_rule");
|
|
254
256
|
if (d.denialLimitFallback !== undefined)
|
|
255
257
|
out.add("denial_limit_fallback");
|
|
258
|
+
if (d.classifierNoRuling !== undefined)
|
|
259
|
+
out.add("classifier_no_ruling");
|
|
256
260
|
if (d.authoredBy === "deployment")
|
|
257
261
|
out.add("operator_forced");
|
|
258
262
|
if (d.probeMandated === true)
|
|
@@ -333,10 +337,14 @@ export function classifyAsk(d, facts) {
|
|
|
333
337
|
return { classes, askClass, row: foldArbitrationRow(classes) };
|
|
334
338
|
}
|
|
335
339
|
export function headlessDenialMessage(toolName, askMessage) {
|
|
336
|
-
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (
|
|
340
|
+
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|
|
337
341
|
}
|
|
338
342
|
export function headlessApprovalRemedy(input) {
|
|
339
|
-
const approver = input.approverSeat === "unavailable"
|
|
343
|
+
const approver = input.approverSeat === "unavailable"
|
|
344
|
+
? "make a person reachable through the approver that is wired (it answered `unavailable` for this ask)"
|
|
345
|
+
: input.approverSeat === "declared_none"
|
|
346
|
+
? 'run it where a person can be asked — this run declares `approvalReach: "none"` (nobody on it can be asked) — or arm a durable approval park so the ask waits for a person'
|
|
347
|
+
: "wire an approver (`onAsk`) to put it to a person";
|
|
340
348
|
const c = input.classification;
|
|
341
349
|
if (input.mode === "acceptEdits" && input.unplacedOptions !== undefined && (c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"))))
|
|
342
350
|
return `${fileCommandOptionsSentence(input.unplacedOptions.program)} Spell the ${input.unplacedOptions.program} without option words for the mode to answer it, or ${approver}`;
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* · `approver_unavailable` — the approver consulted for the call (the run's own seat, or an ancestor's
|
|
6
6
|
* frozen seat on a delegated child) answered the ROUTING question `unavailable` and no durable park
|
|
7
7
|
* took the call afterwards (`tool_end.gate.settlement.kind:"approver_unavailable"`);
|
|
8
|
-
* · `no_approver_wired` — no approver (or, for a question, no question face) is
|
|
9
|
-
* the seat is the deny posture string
|
|
8
|
+
* · `no_approver_wired` — nobody in this run can be asked: no approver (or, for a question, no question face) is
|
|
9
|
+
* wired, the seat is the deny posture string, or the run declared that nobody on it can be asked
|
|
10
|
+
* (`approvalReach: "none"`): the headless auto-deny (`settlement.kind:"no_approver"`);
|
|
10
11
|
* · `mandate_unreconstructible` — an inherited durable-approval mandate binds the ask and THIS leg has no
|
|
11
12
|
* park facility to honor it, so the delegated child refuses in-fold rather than let a synchronous layer
|
|
12
13
|
* resolve what its ancestor bound to a park (an in-fold deny; no settlement rides the frame).
|
|
@@ -6,7 +6,7 @@ export function isAskUnresolvableCause(v) {
|
|
|
6
6
|
}
|
|
7
7
|
const CAUSE_CLAUSE = {
|
|
8
8
|
approver_unavailable: "the approver reported that nobody is reachable for this ask",
|
|
9
|
-
no_approver_wired: "
|
|
9
|
+
no_approver_wired: "nobody in this run can be asked to answer it — the headless posture",
|
|
10
10
|
mandate_unreconstructible: "an inherited durable-approval mandate binds this ask and this leg has no park facility to honor it",
|
|
11
11
|
};
|
|
12
12
|
const CAUSE_REMEDY_SEAT = {
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -166,11 +166,12 @@ export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCaus
|
|
|
166
166
|
*/
|
|
167
167
|
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause, attribution?: AutoModeClassifierRound): string;
|
|
168
168
|
/**
|
|
169
|
-
* The text an ask carries to a person when the classifier
|
|
170
|
-
* (the gate's classifier station and the inherited station): the ask's own text first — the card still
|
|
171
|
-
* is being asked — then why auto mode is asking at all
|
|
169
|
+
* The text an ask carries to a person when the classifier did not rule on it ({@link rungDidNotRule}) and the leg has an
|
|
170
|
+
* approval seat (the gate's classifier station and the inherited station): the ask's own text first — the card still
|
|
171
|
+
* says what is being asked — then why auto mode is asking at all, read off the same cause the ask's `classifierNoRuling`
|
|
172
|
+
* member carries (a client that does not read the member reads this). One writer for both stations.
|
|
172
173
|
*/
|
|
173
|
-
export declare function
|
|
174
|
+
export declare function classifierNoRulingAskMessage(askText: string, cause: AutoModeFallbackCause): string;
|
|
174
175
|
/**
|
|
175
176
|
* The model-facing deny text for a `parse_error` verdict — CC 2.1.259 `i7t` stem ("Auto mode could not evaluate
|
|
176
177
|
* this action and is blocking it for safety"; 2.1.250 `Ure`): the classifier answered, but not with a verdict,
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -84,7 +84,9 @@ export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
|
84
84
|
"If it keeps failing, continue with other tasks that don't require this action and come back to it later. " +
|
|
85
85
|
"Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.");
|
|
86
86
|
}
|
|
87
|
-
export function
|
|
87
|
+
export function classifierNoRulingAskMessage(askText, cause) {
|
|
88
|
+
if (cause === "parse_error")
|
|
89
|
+
return `${askText}\n\nThe auto-mode classifier replied without a verdict, so auto mode could not judge this call; it needs an approval instead of the classifier's verdict.`;
|
|
88
90
|
const parenthetical = cause === "timeout" ? " (timed out)" : "";
|
|
89
91
|
return `${askText}\n\nThe auto-mode classifier is temporarily unavailable${parenthetical}, so auto mode could not judge this call; it needs an approval instead of the classifier's verdict.`;
|
|
90
92
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GateOutcome } from "./gate-outcome.js";
|
|
2
2
|
import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
|
|
3
3
|
import type { CheckpointPause } from "./pause-registry.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type SystemInjectionPriority } from "./task-notification.js";
|
|
5
5
|
import { type QuestionAnswer } from "./ask-question.js";
|
|
6
6
|
import type { ReadEntry } from "../tools/fs/safety.js";
|
|
7
7
|
import type { RepairBundle } from "../agents/repair-loop.js";
|
|
@@ -816,6 +816,8 @@ export type PendingAction = {
|
|
|
816
816
|
* its settlement never reads as the fallback's auto-deny (see `tool_end.autoDenied`). A durable
|
|
817
817
|
* card renders the counts and the limit; it has no countdown to render. Echo-only. */
|
|
818
818
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
819
|
+
/** (additive) The PARK twin of `AskRequest.classifierNoRuling`, written only as a member of its closed set. Display metadata. */
|
|
820
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
819
821
|
/** (additive, same precedent) — the PARK twin of `AskRequest.origin`: WHICH AUTHORITY raised the
|
|
820
822
|
* parked ask, one word from the closed {@link import("./ask-origin.js").AskOrigin} set, the same
|
|
821
823
|
* word the synchronous card is rendered by (engine-stamped at the gate, never a policy's claim).
|
|
@@ -843,6 +845,8 @@ export type PendingAction = {
|
|
|
843
845
|
boundInputHash: string;
|
|
844
846
|
/** A parked `Skill` call's binding to its card — the skill's name and its two lists ({@link import("./skill-grant.js").skillGrantDigest}, judged by {@link import("./skill-grant.js").parkedSkillApprovalRefusal}); only that row carries it. */
|
|
845
847
|
boundGrantDigest?: string;
|
|
848
|
+
/** The value {@link boundGrantDigest} hashes — the card's twin of `AskRequest.boundGrants`, minted by the same factory; written exactly when the digest is. Display metadata (additive, no version bump): the resume judges the digest alone. */
|
|
849
|
+
boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
|
|
846
850
|
/**
|
|
847
851
|
* design/388 §2.5 boundary ② (B18; additive — no checkpoint-version bump, the `previewWithheld`
|
|
848
852
|
* precedent: an optional group an older reader ignores): the IDENTITY of the row the park minted
|
|
@@ -1955,6 +1959,12 @@ export interface CheckpointSummary {
|
|
|
1955
1959
|
* the row to learn them (`autoDenyAfterMs` is `0` on every row — see the row member). Pure echo;
|
|
1956
1960
|
* OMITTED when the row carries none. */
|
|
1957
1961
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1962
|
+
/** The row's `PendingAction.tool_approval.classifierNoRuling`, echoed ONLY as a member of its closed set (else omitted). */
|
|
1963
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
1964
|
+
/** The row's `boundGrantDigest`, echoed (a string; else omitted): the hash of {@link boundGrants}. ECHO-ONLY. */
|
|
1965
|
+
boundGrantDigest?: string;
|
|
1966
|
+
/** The row's `boundGrants` — a fresh copy of the skill and its two lists, published only when it hashes to {@link boundGrantDigest} (never a list beside a digest that binds another). Deployment text: render with contextual escaping. ECHO-ONLY. */
|
|
1967
|
+
boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
|
|
1958
1968
|
/** The row's `PendingAction.tool_approval.origin`, projected for the same one-call reason: a card
|
|
1959
1969
|
* rendered by the ask's origin word must not re-open the row to learn it. Echoed ONLY when the stored
|
|
1960
1970
|
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
@@ -2034,14 +2044,15 @@ export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "g
|
|
|
2034
2044
|
* fabricated "open" still needs the LIVE resolution to say open too. */
|
|
2035
2045
|
| "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing";
|
|
2036
2046
|
/**
|
|
2037
|
-
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the
|
|
2038
|
-
* words prepare's mode, output-style and recorded-mode doors throw, held unchanged so one refusal
|
|
2039
|
-
* run (`TaskResult.errorCode`) and on a resume
|
|
2040
|
-
* `config.output_style_invalid`:
|
|
2041
|
-
*
|
|
2042
|
-
*
|
|
2047
|
+
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the seven
|
|
2048
|
+
* words prepare's mode, output-style, approval-reach, invoked-skill and recorded-mode doors throw, held unchanged so one refusal
|
|
2049
|
+
* reads the same on a fresh run (`TaskResult.errorCode`) and on a resume. `config.spec_retired_key`: a retired spec key;
|
|
2050
|
+
* `config.output_style_invalid`: a malformed output-style seat; `config.permission_mode_invalid`: a mode word outside the closed
|
|
2051
|
+
* set (the config's `permissionMode`, or one the row recorded); `config.shell_gate_value_retired`: a retired shell-floor word;
|
|
2052
|
+
* `config.approval_reach_invalid`: an `approvalReach` outside its set; `config.user_invoked_skill_unknown` / `skill_deny_unreadable`:
|
|
2053
|
+
* `userInvokedSkills` names a skill the config does not carry / one whose `disallowed-tools` entry the grammar reads as nothing.
|
|
2043
2054
|
*/
|
|
2044
|
-
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"];
|
|
2055
|
+
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"];
|
|
2045
2056
|
export type CheckpointConfigRefusalCode = (typeof CHECKPOINT_CONFIG_REFUSAL_CODES)[number];
|
|
2046
2057
|
export declare const isCheckpointConfigRefusalCode: (word: unknown) => word is CheckpointConfigRefusalCode;
|
|
2047
2058
|
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
|
|
@@ -2,11 +2,13 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, PROBE_REASON_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
-
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
5
|
+
import { isAutoModeFallbackCause, readDenialLimitFallback } from "./auto-mode.js";
|
|
6
6
|
import { isAskOrigin, isPersistedRuleMandate } from "./ask-origin.js";
|
|
7
|
+
import { readBoundBinding } from "./skill-grant-binding.js";
|
|
7
8
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
9
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
9
10
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
11
|
+
import { steerPriorityOf } from "./task-notification.js";
|
|
10
12
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
11
13
|
import { isPermissionMode } from "./ask-origin.js";
|
|
12
14
|
import { wireOf } from "./tool-name-readings.js";
|
|
@@ -308,12 +310,14 @@ export function summarizeCheckpoint(cp) {
|
|
|
308
310
|
...(() => {
|
|
309
311
|
const origin = tool?.origin;
|
|
310
312
|
const mandate = tool?.mandate;
|
|
311
|
-
|
|
313
|
+
const why = tool?.classifierNoRuling;
|
|
314
|
+
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isPersistedRuleMandate(mandate) ? { mandate } : {}), ...(isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {}) };
|
|
312
315
|
})(),
|
|
316
|
+
...readBoundBinding(tool),
|
|
313
317
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
314
318
|
};
|
|
315
319
|
}
|
|
316
|
-
export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"]);
|
|
320
|
+
export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"]);
|
|
317
321
|
export const isCheckpointConfigRefusalCode = (word) => CHECKPOINT_CONFIG_REFUSAL_CODES.some((w) => w === word);
|
|
318
322
|
export class CheckpointError extends Error {
|
|
319
323
|
code;
|
|
@@ -547,7 +551,7 @@ export function readPendingSteerQueue(state) {
|
|
|
547
551
|
function samePendingSteerPayload(a, b) {
|
|
548
552
|
return (a.text === b.text &&
|
|
549
553
|
a.trusted === b.trusted &&
|
|
550
|
-
a
|
|
554
|
+
steerPriorityOf(a) === steerPriorityOf(b) &&
|
|
551
555
|
a.actor?.id === b.actor?.id &&
|
|
552
556
|
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
553
557
|
a.actor?.issuer === b.actor?.issuer);
|
|
@@ -28,7 +28,7 @@ import type { TaskSpec } from "./types.js";
|
|
|
28
28
|
* The clamp seats, as string literals (the closed set the scan and the wire-manifest reader both parse —
|
|
29
29
|
* a spread or an identifier here reads as an empty set).
|
|
30
30
|
*/
|
|
31
|
-
export declare const CHILD_CLAMP_SEATS: readonly ["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"];
|
|
31
|
+
export declare const CHILD_CLAMP_SEATS: readonly ["handsReadOnly", "interactiveTools", "approvalReach", "readFace", "writeFace", "readDenyPatterns"];
|
|
32
32
|
/** One clamp seat's name. */
|
|
33
33
|
export type ChildClampSeat = (typeof CHILD_CLAMP_SEATS)[number];
|
|
34
34
|
/**
|
|
@@ -58,6 +58,8 @@ export interface ChildClampSource {
|
|
|
58
58
|
handsReadOnly: boolean | undefined;
|
|
59
59
|
/** The hard-headless clamp: only `false` travels. */
|
|
60
60
|
interactiveTools: boolean | undefined;
|
|
61
|
+
/** The declaration that nobody on the run can be asked to approve: only `"none"` travels. */
|
|
62
|
+
approvalReach: TaskSpec["approvalReach"] | undefined;
|
|
61
63
|
/** The read containment face: only `"roots"` travels. */
|
|
62
64
|
readFace: TaskSpec["readFace"] | undefined;
|
|
63
65
|
/** The write containment face: only `"roots"` travels. */
|
|
@@ -77,6 +79,18 @@ export interface ChildClampSource {
|
|
|
77
79
|
* — so a clamp can never end one level deep, and a new seat cannot reach some constructors and not others.
|
|
78
80
|
*/
|
|
79
81
|
export declare function childClampSeats(source: ChildClampSource): Partial<TaskSpec>;
|
|
82
|
+
/**
|
|
83
|
+
* The interaction posture a leg keeps under the clamps it inherited: `"interactive"` promises a person on the leg, and a
|
|
84
|
+
* leg that inherited the hard-headless clamp (`interactiveTools: false`) or the declaration that nobody can be asked
|
|
85
|
+
* (`approvalReach: "none"`) cannot keep that promise — the clamp is the tightening value, so the posture becomes
|
|
86
|
+
* `"headless"` (stated, so the leg's own resolution does not fall through to a deployment default of `"interactive"`);
|
|
87
|
+
* every other posture is returned as it is. Read where an inherited posture meets inherited clamps: the workflow lane
|
|
88
|
+
* (a baseline's posture beside the host's clamps) and the revival fold (the spawn's posture beside the waker's clamps).
|
|
89
|
+
*/
|
|
90
|
+
export declare function postureUnderClamps(posture: "interactive" | "headless" | undefined, clamps: {
|
|
91
|
+
readonly interactiveTools: boolean | undefined;
|
|
92
|
+
readonly approvalReach: TaskSpec["approvalReach"] | undefined;
|
|
93
|
+
}): "interactive" | "headless" | undefined;
|
|
80
94
|
/**
|
|
81
95
|
* The OWN posture of an engine-built leg that never asks the person (the observer sidecar, the verifier): no
|
|
82
96
|
* human-facing tool is mounted AND the leg declares itself headless. Both, always together — the spec's posture
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export const CHILD_CLAMP_SEATS = Object.freeze(["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"]);
|
|
1
|
+
export const CHILD_CLAMP_SEATS = Object.freeze(["handsReadOnly", "interactiveTools", "approvalReach", "readFace", "writeFace", "readDenyPatterns"]);
|
|
2
2
|
export const CHILD_CLAMP_SEATS_NOT_CARRIED = Object.freeze({
|
|
3
3
|
toolPolicy: "not a clamp a child spec copies: a parent's denies reach its children through the ancestor-constraint chain the child's own prepare folds, not by putting the parent's policy object on the child seat",
|
|
4
4
|
shellGate: "travels on the inherited-gate lane (`InheritedGate.shellGate`, a trusted internals seat), not on the child spec — a spec-level copy would be a second source for the same clamp",
|
|
5
5
|
permissionMode: "travels on the inherited-gate lane (`InheritedGate.permissionMode` / `permissionModeCeiling` — the parent's declared word and its LIVE seat): a child with no word of its own inherits that ceiling on every read, and a word it does declare is clamped under it; a spec-level copy would freeze the parent's word at spawn and be a second source for the same clamp",
|
|
6
|
-
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none",
|
|
6
|
+
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none (a parent that declared nobody can be asked hands down no approver — its frozen seat is empty — and the declaration rides `approvalReach`)",
|
|
7
7
|
hooks: "a deployment seat, not an inherited clamp — `tightenTaskSpec` refuses to merge two hook sets rather than composing them, so there is no tightening value to carry",
|
|
8
8
|
excludeTools: "roster control rather than containment: the lanes that copy a parent's roster do so beside these seats, and the workflow lane authors its child roster from the governance baseline instead of the host's",
|
|
9
9
|
deferTools: "roster control, same reading as `excludeTools` above",
|
|
@@ -21,6 +21,7 @@ export function childClampSeats(source) {
|
|
|
21
21
|
return {
|
|
22
22
|
...(source.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
23
23
|
...(source.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
24
|
+
...(source.approvalReach === "none" ? { approvalReach: "none" } : {}),
|
|
24
25
|
...(source.readFace === "roots" ? { readFace: "roots" } : {}),
|
|
25
26
|
...(source.writeFace === "roots" ? { writeFace: "roots" } : {}),
|
|
26
27
|
...(source.readDenyPatterns !== undefined && source.readDenyPatterns.length > 0
|
|
@@ -28,6 +29,9 @@ export function childClampSeats(source) {
|
|
|
28
29
|
: {}),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
export function postureUnderClamps(posture, clamps) {
|
|
33
|
+
return posture === "interactive" && (clamps.interactiveTools === false || clamps.approvalReach === "none") ? "headless" : posture;
|
|
34
|
+
}
|
|
31
35
|
export function engineHeadlessLegSeats() {
|
|
32
36
|
return { interactiveTools: false, interactionPosture: "headless" };
|
|
33
37
|
}
|