@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/skill-grant.js
CHANGED
|
@@ -3,25 +3,32 @@ import { parsePermissionRule } from "./permission-rules.js";
|
|
|
3
3
|
import { mountedToolPermissionSeat } from "./mode-policy.js";
|
|
4
4
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
5
5
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
6
|
-
import { allowingRows, autoStripped, compileAllowEntry, compileAllowList, judgeAllowEntries } from "./allow-entry.js";
|
|
6
|
+
import { allowingRows, autoStripped, compileAllowEntry, compileAllowList, inertEntryPhrase, judgeAllowEntries } from "./allow-entry.js";
|
|
7
7
|
export const SKILL_GRANT_ORIGINS = Object.freeze(["model", "user", "inherited"]);
|
|
8
8
|
const FROZEN_SKILL_GRANT_ORIGINS = new Set(["model", "inherited"]);
|
|
9
9
|
export function skillNamedBy(args, skills) {
|
|
10
10
|
const named = args?.skill;
|
|
11
11
|
return typeof named === "string" ? skills.find((s) => s.name === named) : undefined;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
function skillGrantBinding(skill) {
|
|
14
14
|
const canonical = (entries) => [...new Set((entries ?? []).map((e) => {
|
|
15
15
|
const p = parsePermissionRule(e);
|
|
16
16
|
return p.ruleContent === undefined ? p.toolName : `${p.toolName}(${p.ruleContent})`;
|
|
17
17
|
}))].sort();
|
|
18
|
-
|
|
18
|
+
const boundGrants = { skill: skill?.name ?? null, allowedTools: canonical(skill?.allowedTools), disallowedTools: canonical(skill?.disallowedTools) };
|
|
19
|
+
return { boundGrantDigest: boundInputHashOf(boundGrants), boundGrants };
|
|
20
|
+
}
|
|
21
|
+
export function skillGrantDigest(skill) {
|
|
22
|
+
return skillGrantBinding(skill).boundGrantDigest;
|
|
19
23
|
}
|
|
20
|
-
export function
|
|
21
|
-
return
|
|
24
|
+
export function skillGrantBindingMembers(call, args, skills, skillToolName) {
|
|
25
|
+
return isSkillActivationCall(call, skills, skillToolName) ? skillGrantBinding(skillNamedBy(args, skills)) : {};
|
|
26
|
+
}
|
|
27
|
+
function isSkillActivationCall(call, skills, skillToolName) {
|
|
28
|
+
return skills.length > 0 && wireOf("skillGrant.binding", call) === skillToolName;
|
|
22
29
|
}
|
|
23
30
|
export function parkedSkillApprovalRefusal(row, args, skills, skillToolName, site = "claim") {
|
|
24
|
-
if (row.boundGrantDigest === undefined &&
|
|
31
|
+
if (row.boundGrantDigest === undefined && skillGrantBindingMembers(row, args, skills, skillToolName).boundGrantDigest === undefined)
|
|
25
32
|
return undefined;
|
|
26
33
|
if (skillGrantDigest(skillNamedBy(args, skills)) === row.boundGrantDigest)
|
|
27
34
|
return undefined;
|
|
@@ -43,18 +50,18 @@ export function detachSkillLists(skills) {
|
|
|
43
50
|
export function declaresSkillLists(skill) {
|
|
44
51
|
return (skill.allowedTools?.length ?? 0) > 0 || (skill.disallowedTools?.length ?? 0) > 0;
|
|
45
52
|
}
|
|
46
|
-
export function compileSkillGrant(skill, origin) {
|
|
53
|
+
export function compileSkillGrant(skill, origin, roster) {
|
|
47
54
|
const rows = [];
|
|
48
55
|
const inert = [];
|
|
49
56
|
for (const [list, behavior, entries] of [
|
|
50
57
|
["allowed-tools", "allow", skill.allowedTools ?? []],
|
|
51
58
|
["disallowed-tools", "deny", skill.disallowedTools ?? []],
|
|
52
59
|
]) {
|
|
53
|
-
const compiled = compileAllowList(entries, behavior);
|
|
60
|
+
const compiled = compileAllowList(entries, behavior, roster);
|
|
54
61
|
for (const r of compiled.rows)
|
|
55
62
|
rows.push({ skill: skill.name, origin, behavior, entry: r.entry, compiled: r.compiled });
|
|
56
63
|
for (const i of compiled.inert)
|
|
57
|
-
inert.push({ skill: skill.name, list,
|
|
64
|
+
inert.push({ skill: skill.name, list, ...i });
|
|
58
65
|
}
|
|
59
66
|
return { rows, inert };
|
|
60
67
|
}
|
|
@@ -65,10 +72,15 @@ function judge(rows, req, behavior, ctx) {
|
|
|
65
72
|
export function skillDenyDecision(hit) {
|
|
66
73
|
return {
|
|
67
74
|
action: "deny",
|
|
68
|
-
message:
|
|
75
|
+
message: hit.readsAsNothing !== undefined
|
|
76
|
+
? skillDenyUnreadable(hit.skill, hit.readsAsNothing, "activation").message
|
|
77
|
+
: `the active skill "${inlineUntrusted(hit.skill, 120)}" lists this tool in disallowed-tools (${hit.entries.map((e) => inlineUntrusted(e, 200)).join(", ")}) — refused for the rest of this run`,
|
|
69
78
|
decisionReason: "skill",
|
|
70
79
|
};
|
|
71
80
|
}
|
|
81
|
+
function unreadDenyOf(inert) {
|
|
82
|
+
return inert.filter((i) => i.list === "disallowed-tools");
|
|
83
|
+
}
|
|
72
84
|
export function skillRowRefusal(lane, req) {
|
|
73
85
|
const hit = lane?.denying(req);
|
|
74
86
|
return hit === undefined ? undefined : skillDenyDecision(hit);
|
|
@@ -83,21 +95,21 @@ export function skillAllowDecision(hit, updatedInput) {
|
|
|
83
95
|
}
|
|
84
96
|
const CARD_ENTRIES_MAX = 10;
|
|
85
97
|
const CARD_ENTRY_MAX_CHARS = 120;
|
|
86
|
-
export function skillActivationQuestion(skill, mode) {
|
|
98
|
+
export function skillActivationQuestion(skill, mode, roster) {
|
|
87
99
|
const head = `Execute skill: ${inlineUntrusted(skill.name, 200)}`;
|
|
88
|
-
const granting = compileSkillGrant(skill, "model").rows.filter((r) => r.behavior === "allow" && !(mode === "auto" && autoStripped(r.compiled, undefined)));
|
|
100
|
+
const granting = compileSkillGrant(skill, "model", roster).rows.filter((r) => r.behavior === "allow" && !(mode === "auto" && autoStripped(r.compiled, undefined)));
|
|
89
101
|
if (granting.length === 0)
|
|
90
102
|
return head;
|
|
91
103
|
const shown = granting.slice(0, CARD_ENTRIES_MAX).map((r) => inlineUntrusted(r.entry, CARD_ENTRY_MAX_CHARS));
|
|
92
104
|
const rest = granting.length - shown.length;
|
|
93
105
|
return `${head}\nApproving also lets these tools be used without asking for the rest of this run: ${shown.join(", ")}${rest > 0 ? ` and ${rest} more` : ""}`;
|
|
94
106
|
}
|
|
95
|
-
export function skillSeatReader(skills) {
|
|
107
|
+
export function skillSeatReader(skills, mounted) {
|
|
96
108
|
if (skills.length === 0)
|
|
97
109
|
return undefined;
|
|
98
110
|
return (args, mode) => {
|
|
99
111
|
const skill = skillNamedBy(args, skills);
|
|
100
|
-
return skill !== undefined && declaresSkillLists(skill) ? { seat: "passthrough", question: skillActivationQuestion(skill, mode) } : undefined;
|
|
112
|
+
return skill !== undefined && declaresSkillLists(skill) ? { seat: "passthrough", question: skillActivationQuestion(skill, mode, mounted?.()) } : undefined;
|
|
101
113
|
};
|
|
102
114
|
}
|
|
103
115
|
export function skillGateWiring(opts) {
|
|
@@ -123,27 +135,68 @@ export function seedSkillGrants(table, opts) {
|
|
|
123
135
|
for (const name of invoked)
|
|
124
136
|
table.activate(named(name), "user");
|
|
125
137
|
}
|
|
126
|
-
|
|
138
|
+
const roster = table.mountedNames();
|
|
139
|
+
return opts.skills.flatMap((s) => compileSkillGrant(s, "model", roster).inert);
|
|
140
|
+
}
|
|
141
|
+
export function screenUserInvokedSkills(config) {
|
|
142
|
+
if (config.userInvokedSkills === undefined)
|
|
143
|
+
return;
|
|
144
|
+
try {
|
|
145
|
+
seedSkillGrants(new SkillGrantTable(), { skills: config.skills ?? [], userInvokedSkills: config.userInvokedSkills, inherited: [] });
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
const code = e?.code;
|
|
149
|
+
if (code === USER_INVOKED_SKILL_UNKNOWN_CODE || code === SKILL_DENY_UNREADABLE_CODE)
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
127
152
|
}
|
|
128
153
|
export class SkillGrantTable {
|
|
129
154
|
rowsHeld = [];
|
|
155
|
+
rowKeys = new Set();
|
|
156
|
+
mounted;
|
|
157
|
+
activations;
|
|
158
|
+
bindMountedNames(mounted) {
|
|
159
|
+
this.mounted = mounted;
|
|
160
|
+
}
|
|
161
|
+
bindSkills(skills, skillToolName) {
|
|
162
|
+
this.activations = { skills, skillToolName };
|
|
163
|
+
}
|
|
164
|
+
mountedNames() {
|
|
165
|
+
return this.mounted?.();
|
|
166
|
+
}
|
|
130
167
|
hold(row) {
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
const key = JSON.stringify([row.skill, row.origin, row.behavior, row.entry]);
|
|
169
|
+
if (this.rowKeys.has(key))
|
|
170
|
+
return;
|
|
171
|
+
this.rowKeys.add(key);
|
|
172
|
+
this.rowsHeld.push(row);
|
|
133
173
|
}
|
|
134
174
|
activate(skill, origin) {
|
|
135
|
-
const { rows, inert } = compileSkillGrant(skill, origin);
|
|
175
|
+
const { rows, inert } = compileSkillGrant(skill, origin, this.mountedNames());
|
|
176
|
+
const unreadDeny = unreadDenyOf(inert);
|
|
177
|
+
if (unreadDeny.length > 0)
|
|
178
|
+
throw skillDenyUnreadable(skill.name, unreadDeny, "activation");
|
|
136
179
|
for (const row of rows)
|
|
137
180
|
this.hold(row);
|
|
138
181
|
return inert;
|
|
139
182
|
}
|
|
140
183
|
seed(records, origin) {
|
|
184
|
+
const roster = this.mountedNames();
|
|
185
|
+
const rows = [];
|
|
186
|
+
const unreadDeny = [];
|
|
141
187
|
for (const r of records) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
188
|
+
const read = compileAllowList([r.entry], r.behavior, roster);
|
|
189
|
+
const row = read.rows[0];
|
|
190
|
+
if (row !== undefined)
|
|
191
|
+
rows.push({ skill: r.skill, origin: r.origin === "model" ? "model" : origin, behavior: r.behavior, entry: r.entry, compiled: row.compiled });
|
|
192
|
+
else if (r.behavior === "deny")
|
|
193
|
+
unreadDeny.push(...read.inert.map((i) => ({ skill: r.skill, ...i })));
|
|
146
194
|
}
|
|
195
|
+
const first = unreadDeny[0];
|
|
196
|
+
if (first !== undefined)
|
|
197
|
+
throw skillDenyUnreadable(first.skill, unreadDeny.filter((i) => i.skill === first.skill), "carried");
|
|
198
|
+
for (const row of rows)
|
|
199
|
+
this.hold(row);
|
|
147
200
|
}
|
|
148
201
|
records() {
|
|
149
202
|
return this.rowsHeld.map((r) => ({ skill: r.skill, behavior: r.behavior, entry: r.entry, origin: r.origin }));
|
|
@@ -151,9 +204,17 @@ export class SkillGrantTable {
|
|
|
151
204
|
lane(ctx) {
|
|
152
205
|
return {
|
|
153
206
|
allowing: (req, mode) => judge(allowingRows(this.rowsHeld, req, mode), req, "allow", ctx),
|
|
154
|
-
denying: (req) => judge(this.rowsHeld, req, "deny", ctx),
|
|
207
|
+
denying: (req) => judge(this.rowsHeld, req, "deny", ctx) ?? this.activationRefused(req),
|
|
155
208
|
};
|
|
156
209
|
}
|
|
210
|
+
activationRefused(req) {
|
|
211
|
+
const at = this.activations;
|
|
212
|
+
if (at === undefined || !isSkillActivationCall(req, at.skills, at.skillToolName))
|
|
213
|
+
return undefined;
|
|
214
|
+
const skill = skillNamedBy(req.args, at.skills);
|
|
215
|
+
const unread = skill === undefined ? [] : unreadDenyOf(compileSkillGrant(skill, "model", this.mountedNames()).inert);
|
|
216
|
+
return skill === undefined || unread.length === 0 ? undefined : { skill: skill.name, entries: unread.map((i) => i.entry), readsAsNothing: unread };
|
|
217
|
+
}
|
|
157
218
|
}
|
|
158
219
|
function readCarriedRecords(raw, carrier) {
|
|
159
220
|
if (raw === undefined)
|
|
@@ -190,3 +251,15 @@ export function readFrozenSkillGrants(section) {
|
|
|
190
251
|
return readCarriedRecords(section, "frozen");
|
|
191
252
|
}
|
|
192
253
|
export const USER_INVOKED_SKILL_UNKNOWN_CODE = "config.user_invoked_skill_unknown";
|
|
254
|
+
export const SKILL_DENY_UNREADABLE_CODE = "config.skill_deny_unreadable";
|
|
255
|
+
function skillDenyUnreadable(skill, entries, site) {
|
|
256
|
+
const listed = entries.map(inertEntryPhrase).join(", ");
|
|
257
|
+
const one = entries.length === 1;
|
|
258
|
+
const name = inlineUntrusted(skill, 120);
|
|
259
|
+
const message = site === "activation"
|
|
260
|
+
? `Skill "${name}" was not activated: its disallowed-tools entr${one ? `y ${listed} reads` : `ies ${listed} read`} as nothing — a refusal this engine cannot read is not dropped, so the activation does not happen (its instructions are not loaded and nothing it lists is granted). Spell each entry as the tool is mounted, or remove it, in its definition.`
|
|
261
|
+
: `the grant rows carried for Skill "${name}" (a park's frozen rows, or a parent's capture) hold the disallowed-tools entr${one ? `y ${listed}, which reads` : `ies ${listed}, which read`} as nothing for this run — a refusal this engine cannot read is not dropped, so this leg does not start without it. A parked task an earlier release wrote can be finished on a worker of that release; otherwise correct the entry in its disallowed-tools and submit the task again.`;
|
|
262
|
+
const e = new Error(message);
|
|
263
|
+
e.code = SKILL_DENY_UNREADABLE_CODE;
|
|
264
|
+
return e;
|
|
265
|
+
}
|
|
@@ -30,6 +30,12 @@ export declare const SYSTEM_INJECTION_PRIORITIES: readonly SystemInjectionPriori
|
|
|
30
30
|
* no-subscriber retain path indexes `queues[priority]` and the park path stores it verbatim, so an
|
|
31
31
|
* unknown value used to travel until something downstream failed on it (or didn't, and lied). */
|
|
32
32
|
export declare function isSystemInjectionPriority(value: unknown): value is SystemInjectionPriority;
|
|
33
|
+
/** The ONE reading of a steer's priority: a parked row or an accepted input that carries none means `"next"`
|
|
34
|
+
* (the default the live admission has always applied) — so a retry that spells the default and a row parked
|
|
35
|
+
* without it are the same instruction, whichever side normalized. */
|
|
36
|
+
export declare function steerPriorityOf(x: {
|
|
37
|
+
readonly priority?: SystemInjectionPriority;
|
|
38
|
+
}): SystemInjectionPriority;
|
|
33
39
|
export interface TaskNotificationPayload {
|
|
34
40
|
task_id: string;
|
|
35
41
|
/** design/144 §2: `"external"` is CORE-MINTED ONLY — `TaskStream.notify()` casts it onto every
|
|
@@ -10,6 +10,9 @@ export const SYSTEM_INJECTION_PRIORITIES = ["now", "next", "later"];
|
|
|
10
10
|
export function isSystemInjectionPriority(value) {
|
|
11
11
|
return typeof value === "string" && SYSTEM_INJECTION_PRIORITIES.includes(value);
|
|
12
12
|
}
|
|
13
|
+
export function steerPriorityOf(x) {
|
|
14
|
+
return x.priority ?? "next";
|
|
15
|
+
}
|
|
13
16
|
const TASK_NOTIFICATION_TAGS = [
|
|
14
17
|
"task-notification",
|
|
15
18
|
"task-id",
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -110,6 +110,18 @@ export interface TaskSpec {
|
|
|
110
110
|
* explicit opt-out, which covers both arms.
|
|
111
111
|
*/
|
|
112
112
|
interactionPosture?: "interactive" | "headless";
|
|
113
|
+
/**
|
|
114
|
+
* The run's declaration that NOBODY on it can be asked to approve a call — read by the approval seat's one resolution
|
|
115
|
+
* ({@link import("./wiring-manifest.js").resolveAskSeat}). `"none"`: an approver callback the spec or the deployment wires
|
|
116
|
+
* is not this run's approver (never called; the manifest reports `ask.form: "declared_none"`); with the deployment's park
|
|
117
|
+
* facility armed an ask parks for a person to answer later (`park_only`), without one it is refused as nobody's. It
|
|
118
|
+
* approves and releases nothing; a blanket `"allow"` / `"deny"` seat stays a policy; under `dontAsk` the mode refuses
|
|
119
|
+
* first. Absent = today's reading. Not checkpointed (a resumed leg holds it only when its taskConfig supplies it again);
|
|
120
|
+
* children inherit it (the clamp seats). `interactionPosture: "interactive"` beside it is refused
|
|
121
|
+
* (`config.interaction_posture`); a value outside the closed set too (`config.approval_reach_invalid`). Independent of
|
|
122
|
+
* {@link interactiveTools} — a content question still goes to `onQuestion`.
|
|
123
|
+
*/
|
|
124
|
+
approvalReach?: import("./wiring-manifest.js").ApprovalReach;
|
|
113
125
|
/**
|
|
114
126
|
* design/173 §8.2 — the EXPLICIT knob (default off) letting a posture-`"interactive"` run
|
|
115
127
|
* CONTINUE with the synthetic self-answer instruction after all on BOTH hardened question arms
|
|
@@ -562,7 +574,8 @@ export interface TaskSpec {
|
|
|
562
574
|
* projections and the deny-narrowing layers still apply regardless).
|
|
563
575
|
*/
|
|
564
576
|
basePolicyForResumeEdit?: import("./tool-policy.js").ToolPolicy;
|
|
565
|
-
/** How `ask` decisions resolve for this task (headless auto-deny by default). Overrides `RunnerDeps.onAsk
|
|
577
|
+
/** How `ask` decisions resolve for this task (headless auto-deny by default). Overrides `RunnerDeps.onAsk` when present.
|
|
578
|
+
* A callback here is set aside when the run declares {@link approvalReach} `"none"`. */
|
|
566
579
|
onAsk?: import("./tool-policy.js").OnAsk;
|
|
567
580
|
/** Content-ask seam (design/64 §5): routes an AskUserQuestion tool call to a real human/UI. When set, the
|
|
568
581
|
* AskUserQuestion tool is mounted (one of the delivery faces in the criterion — see
|
|
@@ -13,6 +13,8 @@ export declare class TaskSpecTightenError extends Error {
|
|
|
13
13
|
* would drop the base policy entirely.
|
|
14
14
|
* - **`handsReadOnly`** — `true` is stricter. `base:true` + `override:false` ⇒ **throw** (loosening). Otherwise
|
|
15
15
|
* the stricter (`true` if either is true) wins.
|
|
16
|
+
* - **`approvalReach`** — `"none"` (nobody can be asked) is the only word; a base declaration survives an override that
|
|
17
|
+
* omits it or writes `undefined`.
|
|
16
18
|
* - **`shellGate`** — one value (`"always"`, the operator floor). Either side set ⇒ the floor stands; it cannot
|
|
17
19
|
* be loosened because absence is the only other state and an override cannot unset a base value.
|
|
18
20
|
* - **`onAsk`** — no safe automatic merge (the approver and the policy must be coordinated). Both set to
|
|
@@ -47,6 +47,9 @@ export function tightenTaskSpec(base, overrides) {
|
|
|
47
47
|
if (base.handsReadOnly !== undefined || overrides.handsReadOnly !== undefined) {
|
|
48
48
|
merged.handsReadOnly = base.handsReadOnly === true || overrides.handsReadOnly === true;
|
|
49
49
|
}
|
|
50
|
+
if (base.approvalReach !== undefined || overrides.approvalReach !== undefined) {
|
|
51
|
+
merged.approvalReach = base.approvalReach === "none" || overrides.approvalReach === "none" ? "none" : (overrides.approvalReach ?? base.approvalReach);
|
|
52
|
+
}
|
|
50
53
|
if (base.permissionMode !== undefined && overrides.permissionMode !== undefined) {
|
|
51
54
|
const { effective, clamped } = clampPermissionMode(overrides.permissionMode, base.permissionMode);
|
|
52
55
|
if (clamped) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ProtocolNamespace } from "./protocol-table.js";
|
|
1
2
|
/**
|
|
2
3
|
* RB-476-A stage 2 (ruling 2026-08-02, RB-476 "no compatibility surface survives", replay
|
|
3
4
|
* resolution included) — the durable-name machinery this module used to hold is RETIRED:
|
|
@@ -26,17 +27,23 @@
|
|
|
26
27
|
* wider tool: writing it would deny more than the entry did, so it is no current name), or `removed — `.
|
|
27
28
|
*/
|
|
28
29
|
export declare const RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
|
|
29
|
-
/** The
|
|
30
|
-
export declare const RULE_TOOL_NAME_VERDICTS: readonly ["current", "retired", "unprefixed_namespace"];
|
|
30
|
+
/** The four answers {@link judgeRuleToolName} gives — a closed set. */
|
|
31
|
+
export declare const RULE_TOOL_NAME_VERDICTS: readonly ["current", "retired", "unprefixed_namespace", "unmintable_segment"];
|
|
31
32
|
/**
|
|
32
33
|
* Whether a tool name written in a rule is a stale name — one that, against `roster`, names no tool and never can:
|
|
33
|
-
* - `current` — not a stale name: the roster mounts it, or it is
|
|
34
|
-
*
|
|
34
|
+
* - `current` — not a stale name: the roster mounts it, or it is none of the three below. (Whether a current name matches
|
|
35
|
+
* anything THIS run is a different question — an unknown name is the prepare audit's advisory.)
|
|
35
36
|
* - `retired` — a name in {@link RETIRED_TOOL_NAMES} the roster does not mount; `guidance` is that table's line, and
|
|
36
37
|
* `currentName` the 1:1 successor when there is one (see the module header's grammar).
|
|
37
38
|
* - `unprefixed_namespace` — carries the `__` namespace separator with no protocol prefix. The roster cannot clear it:
|
|
38
39
|
* a caller tool name or alias carrying `__` is refused at prepare, every engine tool name is `__`-free, and every
|
|
39
40
|
* protocol tool is minted with its prefix.
|
|
41
|
+
* - `unmintable_segment` — carries a protocol prefix, but its peer segment (the server / remote agent) is not a spelling
|
|
42
|
+
* the mint produces for any peer: the name is read as written (peers compare exactly), and every mounted tool carries
|
|
43
|
+
* the minted spelling, so the name reaches none of them. `guidance` says so in one line; `currentName` is the whole name
|
|
44
|
+
* rewritten with the spelling the mint produces (`mcp__github.com__*` → `mcp__github_com__*`), absent when the peer
|
|
45
|
+
* segment is empty and there is nothing to rewrite. The tool segment after the peer is never judged: it is a literal
|
|
46
|
+
* tool name or a glob over one, and an unmatched one is the audit's advisory.
|
|
40
47
|
*
|
|
41
48
|
* `roster` = the tool names (aliases included) the reader knows are mounted: a run's assembled roster at prepare, the
|
|
42
49
|
* names a deployment DECLARES it mounts its own tools under at a session-rule store (an empty set when it declares none).
|
|
@@ -49,6 +56,29 @@ export type RuleToolNameVerdict = {
|
|
|
49
56
|
readonly currentName?: string;
|
|
50
57
|
} | {
|
|
51
58
|
readonly kind: "unprefixed_namespace";
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: "unmintable_segment";
|
|
61
|
+
readonly guidance: string;
|
|
62
|
+
readonly currentName?: string;
|
|
52
63
|
};
|
|
53
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* A name in ANY protocol namespace split into its owning namespace, its peer segment and its optional tool segment —
|
|
66
|
+
* CC `rP` (:70710) verbatim, re-anchored on the {@link protocolOf} lookup instead of CC's literal `"mcp"` first split element.
|
|
67
|
+
* `undefined` for a name that is not namespaced (no protocol prefix). The peer is the first segment — EMPTY for `mcp____x` and
|
|
68
|
+
* for the bare prefix, which name no peer; the tool segment is the REST of the split rejoined, so a tool whose minted segment
|
|
69
|
+
* itself carries the separator (charset normalization maps `get..issue` → `get__issue`) decomposes back to one tool name, and
|
|
70
|
+
* is absent when the name stops at the peer (`mcp__srv`). The one decomposition: the judgment, the covering matcher and the
|
|
71
|
+
* allow-entry grammar all read a namespaced name through it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function splitNamespacedName(name: string): {
|
|
74
|
+
readonly ns: ProtocolNamespace;
|
|
75
|
+
readonly peer: string;
|
|
76
|
+
readonly tool?: string;
|
|
77
|
+
} | undefined;
|
|
78
|
+
/** See {@link RuleToolNameVerdict}. Pure; the one answer to "is this rule's tool name a name that reaches no tool". */
|
|
54
79
|
export declare function judgeRuleToolName(name: string, roster: ReadonlySet<string>): RuleToolNameVerdict;
|
|
80
|
+
/** The names a run mounts — every tool's primary name and its aliases: the roster {@link judgeRuleToolName} is given. */
|
|
81
|
+
export declare function mountedToolNames(tools: ReadonlyArray<{
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly aliases?: readonly string[];
|
|
84
|
+
}>): Set<string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { carriesNamespaceSeparator, protocolOf } from "./protocol-table.js";
|
|
2
|
+
import { mintNamespacePrefix } from "./protocol-naming.js";
|
|
2
3
|
export const RETIRED_TOOL_NAMES = new Map([
|
|
3
4
|
["read_file", 'renamed "Read"'],
|
|
4
5
|
["edit_file", 'renamed "Edit"'],
|
|
@@ -41,9 +42,29 @@ export const RETIRED_TOOL_NAMES = new Map([
|
|
|
41
42
|
["Recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
|
|
42
43
|
["recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
|
|
43
44
|
]);
|
|
44
|
-
export const RULE_TOOL_NAME_VERDICTS = Object.freeze(["current", "retired", "unprefixed_namespace"]);
|
|
45
|
+
export const RULE_TOOL_NAME_VERDICTS = Object.freeze(["current", "retired", "unprefixed_namespace", "unmintable_segment"]);
|
|
45
46
|
const CURRENT = Object.freeze({ kind: "current" });
|
|
46
47
|
const UNPREFIXED = Object.freeze({ kind: "unprefixed_namespace" });
|
|
48
|
+
const NAMESPACE_SEPARATOR = "__";
|
|
49
|
+
export function splitNamespacedName(name) {
|
|
50
|
+
const ns = protocolOf(name);
|
|
51
|
+
if (ns === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
const [peer = "", ...rest] = name.slice(ns.prefix.length).split(NAMESPACE_SEPARATOR);
|
|
54
|
+
return rest.length > 0 ? { ns, peer, tool: rest.join(NAMESPACE_SEPARATOR) } : { ns, peer };
|
|
55
|
+
}
|
|
56
|
+
function unmintableSegmentOf({ ns, peer, tool }) {
|
|
57
|
+
if (peer === "")
|
|
58
|
+
return { kind: "unmintable_segment", guidance: `the ${ns.id} name has an empty peer segment — no tool is minted under it` };
|
|
59
|
+
const minted = mintNamespacePrefix(ns, peer).slice(ns.prefix.length, -NAMESPACE_SEPARATOR.length);
|
|
60
|
+
if (minted === peer)
|
|
61
|
+
return undefined;
|
|
62
|
+
return {
|
|
63
|
+
kind: "unmintable_segment",
|
|
64
|
+
guidance: `the ${ns.id} peer segment ${JSON.stringify(peer)} is not a spelling the engine mints — its tools are named ${ns.prefix}${minted}${NAMESPACE_SEPARATOR}<tool>, and a rule's peer segment must be spelled the way its tools are`,
|
|
65
|
+
currentName: `${ns.prefix}${minted}${tool !== undefined ? `${NAMESPACE_SEPARATOR}${tool}` : ""}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
47
68
|
export function judgeRuleToolName(name, roster) {
|
|
48
69
|
if (roster.has(name))
|
|
49
70
|
return CURRENT;
|
|
@@ -52,7 +73,11 @@ export function judgeRuleToolName(name, roster) {
|
|
|
52
73
|
const currentName = /^renamed "([^"]+)"/.exec(guidance)?.[1];
|
|
53
74
|
return currentName !== undefined ? { kind: "retired", guidance, currentName } : { kind: "retired", guidance };
|
|
54
75
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
const split = splitNamespacedName(name);
|
|
77
|
+
if (split === undefined)
|
|
78
|
+
return carriesNamespaceSeparator(name) ? UNPREFIXED : CURRENT;
|
|
79
|
+
return unmintableSegmentOf(split) ?? CURRENT;
|
|
80
|
+
}
|
|
81
|
+
export function mountedToolNames(tools) {
|
|
82
|
+
return new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
58
83
|
}
|
|
@@ -303,6 +303,9 @@ export type PermissionResult = {
|
|
|
303
303
|
* same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
|
|
304
304
|
* route it would be a window for has not been chosen yet. */
|
|
305
305
|
denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
|
|
306
|
+
/** WHY a classifier station handed this call to a person: the classification did not rule (`error` / `timeout` — it could
|
|
307
|
+
* not run; `parse_error` — no verdict). Engine-stamped with `requiresRealApproval`; class `classifier_no_ruling`. */
|
|
308
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
306
309
|
/** The allow layer's DISCLOSURE: the members that MATCHED this call and could not clear the ask (it is a class
|
|
307
310
|
* no allow rule clears — a mandated, governance, hook, ask-rule or mode question — or the call reached the layer
|
|
308
311
|
* marked), one record per member ({@link AllowRuleShadow}). ENGINE-STAMPED by the allow layer, overwriting a
|
|
@@ -1201,6 +1204,10 @@ export interface AskRequest {
|
|
|
1201
1204
|
* mint site added later is covered by construction); optional on the type because a deployment may
|
|
1202
1205
|
* invoke its approver function directly. */
|
|
1203
1206
|
readonly boundInputHash?: string;
|
|
1207
|
+
/** A call to the engine's `Skill` tool: the hash of {@link boundGrants} — the digest a parked approval of the same card is judged by, so a live card reconciles against its parked row by `(toolCallId, boundGrantDigest)` and an answer is remembered by "this tool + this binding". Present exactly on that call (whoever asked, however the question was worded), absent on every other card; `message` renders the same lists narrowed to what releases in the leg's mode. Reconciliation metadata, never adjudication input. */
|
|
1208
|
+
readonly boundGrantDigest?: string;
|
|
1209
|
+
/** The value {@link boundGrantDigest} hashes: `{ skill, allowedTools, disallowedTools }` in canonical spelling (de-duplicated, sorted), `skill: null` when no loaded skill is named; a fresh copy per ask; same presence. Display metadata (deployment text — render with contextual escaping). */
|
|
1210
|
+
readonly boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
|
|
1204
1211
|
/**
|
|
1205
1212
|
* design/179 §4, re-formed by design/375 §3.1 (BREAKING — this seat REPLACES the retired
|
|
1206
1213
|
* `ruleSuggestions` key, which is never emitted again; the new name is the safe failure posture for
|
|
@@ -1396,6 +1403,9 @@ export interface AskRequest {
|
|
|
1396
1403
|
* that forwards a decision's member here does not compile, which is the point: the window is the
|
|
1397
1404
|
* route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
|
|
1398
1405
|
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1406
|
+
/** The {@link PermissionResult} ask-arm member of the same name (class `classifier_no_ruling`), carried by the four mint
|
|
1407
|
+
* stations; the `message`'s second paragraph says the same for a client that does not read this. */
|
|
1408
|
+
readonly classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
1399
1409
|
/** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
|
|
1400
1410
|
* member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
|
|
1401
1411
|
* The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -259,7 +259,7 @@ const ALLOW = Object.freeze({ action: "allow" });
|
|
|
259
259
|
const ALLOW_BY_RULE = Object.freeze({ action: "allow", decisionReason: "rule" });
|
|
260
260
|
const RETIRED_TEXT_FIELD = "reason";
|
|
261
261
|
export const DECISION_MEMBERS = Object.keys({
|
|
262
|
-
action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, allowRuleShadowed: 0, probeReason: 0,
|
|
262
|
+
action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, classifierNoRuling: 0, allowRuleShadowed: 0, probeReason: 0,
|
|
263
263
|
probeCause: 0, probeMandated: 0, mandated: 0, mandate: 0, probeReadRootCandidate: 0, matchedAskRule: 0, ruleEvidence: 0, segmentCoverage: 0,
|
|
264
264
|
origin: 0, askClass: 0, classes: 0, guard: 0, ruleUnreadable: 0, authoredBy: 0, foldedAskClasses: 0,
|
|
265
265
|
});
|
|
@@ -551,6 +551,7 @@ export function combinePolicies(...policies) {
|
|
|
551
551
|
let probeMandateSeen = false;
|
|
552
552
|
let mandateSeen = false;
|
|
553
553
|
let fallbackSeen;
|
|
554
|
+
let noRulingSeen;
|
|
554
555
|
let authoredBySeen = false;
|
|
555
556
|
const contributors = [];
|
|
556
557
|
const settleDeny = (d) => {
|
|
@@ -597,6 +598,8 @@ export function combinePolicies(...policies) {
|
|
|
597
598
|
mandateSeen = true;
|
|
598
599
|
if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
|
|
599
600
|
fallbackSeen = d.denialLimitFallback;
|
|
601
|
+
if (d.action === "ask" && d.classifierNoRuling !== undefined && noRulingSeen === undefined)
|
|
602
|
+
noRulingSeen = d.classifierNoRuling;
|
|
600
603
|
if (d.action === "ask" && d.authoredBy === "deployment")
|
|
601
604
|
authoredBySeen = true;
|
|
602
605
|
if (d.action === "ask")
|
|
@@ -610,12 +613,16 @@ export function combinePolicies(...policies) {
|
|
|
610
613
|
const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
|
|
611
614
|
const withMandate = mandateSeen && withMark.mandated !== true ? { ...withMark, mandated: true } : withMark;
|
|
612
615
|
const withFallback = fallbackSeen !== undefined && withMandate.denialLimitFallback === undefined ? { ...withMandate, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMandate;
|
|
613
|
-
const
|
|
616
|
+
const withNoRuling = noRulingSeen !== undefined && withFallback.classifierNoRuling === undefined ? { ...withFallback, classifierNoRuling: noRulingSeen, requiresRealApproval: true } : withFallback;
|
|
617
|
+
const withAuthor = authoredBySeen && withNoRuling.authoredBy === undefined ? { ...withNoRuling, authoredBy: "deployment" } : withNoRuling;
|
|
614
618
|
const folded = new Set(withAuthor.foldedAskClasses ?? []);
|
|
615
619
|
for (const c of contributors)
|
|
616
620
|
if (c !== asked)
|
|
617
621
|
for (const k of decisionAskClassesOf(c))
|
|
618
622
|
folded.add(k);
|
|
623
|
+
if (withNoRuling !== withMandate)
|
|
624
|
+
for (const k of decisionAskClassesOf(asked))
|
|
625
|
+
folded.add(k);
|
|
619
626
|
const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
|
|
620
627
|
const out = merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
|
|
621
628
|
if (folded.size > 0)
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -952,6 +952,13 @@ export interface ToolExecuteContext {
|
|
|
952
952
|
* is deliberately NOT inherited).
|
|
953
953
|
*/
|
|
954
954
|
interactiveTools?: false;
|
|
955
|
+
/**
|
|
956
|
+
* The run's declaration that nobody on it can be asked to approve ({@link TaskSpec.approvalReach} `"none"`),
|
|
957
|
+
* Runner-filled read-only on the same trusted seat, present ONLY when declared. A delegation tool copies it onto every
|
|
958
|
+
* child spec (the clamp carrier): the child's own seat resolution would otherwise fall back to the deployment's live
|
|
959
|
+
* approver, because a declared-none parent hands down no approver of its own ({@link onAsk} is absent on its ctx).
|
|
960
|
+
*/
|
|
961
|
+
approvalReach?: "none";
|
|
955
962
|
/**
|
|
956
963
|
* Read-only mirror of {@link "./types.js".TaskSpec.oneShot} (same trust tier/seat as
|
|
957
964
|
* `principal`): this RUN has no later turn for an async background notification to land in (a
|
|
@@ -26,6 +26,7 @@ import { type StoreDurability } from "./checkpoint-store.js";
|
|
|
26
26
|
import type { WiringManifestHookEntry } from "./hook-seats.js";
|
|
27
27
|
import type { McpDelivered, McpFailureKind, McpServerLiveness } from "./mcp-failure.js";
|
|
28
28
|
import type { WriteProtectionTargetView } from "./write-protect.js";
|
|
29
|
+
import type { OnAsk } from "./tool-policy.js";
|
|
29
30
|
export type { StoreDurability };
|
|
30
31
|
/** The manifest-face projection of {@link StoreDurability}: `"declared_durable"` (the name says it —
|
|
31
32
|
* a DECLARATION relayed, not a verification) vs `"process_local"` (declared so, or fail-closed
|
|
@@ -37,11 +38,29 @@ export type ManifestDurability = "declared_durable" | "process_local";
|
|
|
37
38
|
* child's manifest rides the CHILD's event stream and is deliberately not in the parent-forwarding
|
|
38
39
|
* whitelist — a host that wants a child's assembly evidence subscribes to the child. */
|
|
39
40
|
export type WiringLegKind = "root" | "child" | "resume";
|
|
40
|
-
/** The shape of the resolved permission-ask seat (
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
41
|
+
/** The shape of the resolved permission-ask seat ({@link resolveAskSeat}): a live approver callback, a blanket
|
|
42
|
+
* `"allow"`/`"deny"` policy setting, nothing at all, or `"declared_none"` — the run declared that nobody on it can be
|
|
43
|
+
* asked (`TaskSpec.approvalReach: "none"`), so a callback beside the declaration is not this run's approver. `"deny"` (a
|
|
44
|
+
* deliberate policy) and `"absent"` (no seat supplied a value — a degrade to the headless auto-deny default) are DISTINCT
|
|
45
|
+
* on purpose — design/173 — and so are `"absent"` and `"declared_none"`: one says nothing was wired, the other that
|
|
46
|
+
* the run said nobody can answer. */
|
|
47
|
+
export type AskSeamForm = "callback" | "allow" | "deny" | "absent" | "declared_none";
|
|
48
|
+
/**
|
|
49
|
+
* The closed set of a run's approval-reach declaration (`TaskSpec.approvalReach`): `"none"` — nobody on this run can be
|
|
50
|
+
* asked to approve. It removes the one seat that could be a person (a callback); it never approves or releases anything.
|
|
51
|
+
* There is no positive word: "a person is reachable" is the `interactionPosture: "interactive"` promise, which has its own
|
|
52
|
+
* door.
|
|
53
|
+
*/
|
|
54
|
+
export declare const APPROVAL_REACH: readonly ["none"];
|
|
55
|
+
export type ApprovalReach = (typeof APPROVAL_REACH)[number];
|
|
56
|
+
/** Membership test for {@link ApprovalReach}. */
|
|
57
|
+
export declare function isApprovalReach(v: unknown): v is ApprovalReach;
|
|
58
|
+
/**
|
|
59
|
+
* The declaration's door: absent passes as absent, a member passes, anything else present is refused loudly
|
|
60
|
+
* (`config.approval_reach_invalid`) — a declaration nobody can read must not fold to "a person may be asked" (or to
|
|
61
|
+
* "nobody may"). Prepare, the static half and a resume's pre-claim screen call this one function.
|
|
62
|
+
*/
|
|
63
|
+
export declare function screenApprovalReach(v: unknown): ApprovalReach | undefined;
|
|
45
64
|
/** What a policy `ask` actually reaches under this assembly. `"unresolved"` = not computable from
|
|
46
65
|
* the facts at hand (the static half's park-dependent arm). */
|
|
47
66
|
export type AskEffective = "human_reachable" | "auto_allow" | "auto_deny" | "park_only" | "unresolved";
|
|
@@ -178,8 +197,8 @@ export interface WiringManifest {
|
|
|
178
197
|
peerPresence?: import("../agents/peer-directory.js").PeerPresence;
|
|
179
198
|
ask: {
|
|
180
199
|
form: AskSeamForm;
|
|
181
|
-
/** Absent iff
|
|
182
|
-
* a false fact
|
|
200
|
+
/** Absent iff no seat supplied a value (`"absent"`, or `"declared_none"` with nothing wired) — a fabricated provenance
|
|
201
|
+
* would be a false fact. */
|
|
183
202
|
provenance?: SeamProvenance;
|
|
184
203
|
/** OMITTED on the static half (design/173 §8.4): the absent-form arm depends on the park lane,
|
|
185
204
|
* which the static half cannot always resolve. Always present on the effective half. */
|
|
@@ -556,7 +575,7 @@ export interface WiringFacts {
|
|
|
556
575
|
* single-source shapes, no parallel hand-copied interface). */
|
|
557
576
|
export type StaticWiringDeps = Pick<RunnerDeps, "onAsk" | "onQuestion" | "interactionPosture" | "onElicit" | "checkpointStore" | "sessionStore" | "backgroundAgentStore" | "onBackgroundChildEvent" | "lockedConfig" | "compliancePostureResolver" | "memoryScopeAdmission" | "retentionPolicy" | "permissionRuleStore" | "permissionRuleSyncWired">;
|
|
558
577
|
/** Named view of the spec seats the static half reads (a `Pick` of the real {@link TaskSpec}). */
|
|
559
|
-
export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "onQuestion" | "checkpointStore" | "durableApproval" | "mcp" | "interactiveTools" | "interactionPosture">;
|
|
578
|
+
export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "approvalReach" | "onQuestion" | "checkpointStore" | "durableApproval" | "mcp" | "interactiveTools" | "interactionPosture">;
|
|
560
579
|
/**
|
|
561
580
|
* Read a store's {@link StoreDurability} declaration: absent ⇒ `"process-local"` (fail-closed —
|
|
562
581
|
* under-promise, never over-promise), a declared value passes through, and an UNPARSEABLE value is
|
|
@@ -613,8 +632,9 @@ export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean,
|
|
|
613
632
|
* so on an armed park lane the ask parks and the honest report is `park_only` — reporting the
|
|
614
633
|
* blanket as what the ask "reaches" would be a false fact of exactly the kind this module refuses.
|
|
615
634
|
* Off the park lane the blanket is what happens: the deliberate auto policies;
|
|
616
|
-
* - no seat at all ⇒ the park lane decides: armed ⇒
|
|
617
|
-
*
|
|
635
|
+
* - no seat at all, or a run that declared nobody can be asked (`declared_none`) ⇒ the park lane decides: armed ⇒
|
|
636
|
+
* `park_only` (the deployment's park is its own statement that an answer can wait for a person), unresolved ⇒
|
|
637
|
+
* `unresolved`, else the headless degrade `auto_deny`.
|
|
618
638
|
*/
|
|
619
639
|
export declare function deriveAskEffective(form: AskSeamForm, parkEffective: boolean | "unresolved", mode?: PermissionMode): AskEffective;
|
|
620
640
|
/**
|
|
@@ -637,10 +657,32 @@ export declare function askReachesPerson(ask: {
|
|
|
637
657
|
readonly effective?: AskEffective;
|
|
638
658
|
}): boolean;
|
|
639
659
|
export declare function deriveWiringManifest(facts: WiringFacts): WiringManifest;
|
|
640
|
-
/**
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
export
|
|
660
|
+
/** A run's approval seat as its one resolution froze it: the value every reader that needs the approver itself reads
|
|
661
|
+
* (`undefined` when nothing on this run can be asked), the form the manifest reports and every reader of the word derives
|
|
662
|
+
* from, the seat that supplied the value, and the declaration when the run made one. */
|
|
663
|
+
export interface AskSeat {
|
|
664
|
+
readonly value: OnAsk | undefined;
|
|
665
|
+
readonly form: AskSeamForm;
|
|
666
|
+
/** Absent iff no seat supplied a value. A callback the declaration set aside still names its seat. */
|
|
667
|
+
readonly provenance?: SeamProvenance;
|
|
668
|
+
readonly reach?: ApprovalReach;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* THE approval-seat resolution — the one place a run's `spec.onAsk` / `deps.onAsk` are read. Prepare calls it once, at
|
|
672
|
+
* its config doors (before any resource is taken), and hands the frozen result down; the static half calls it for its
|
|
673
|
+
* own self-check. The seat is the spec's when the spec's key is PRESENT, else the deployment's (presence, not `??`: a
|
|
674
|
+
* spec `null` is a junk value, never a fall-through to the deployment). A value outside the `OnAsk` union is refused
|
|
675
|
+
* loudly (`config.interaction_wiring`), a declaration outside {@link APPROVAL_REACH} too (`config.approval_reach_invalid`).
|
|
676
|
+
*
|
|
677
|
+
* `approvalReach: "none"` is one sentence here: a blanket `"allow"` / `"deny"` stays what it is (a policy setting never
|
|
678
|
+
* claimed to be a person); a callback, or no seat, becomes `declared_none` with NO value — so the readers of the value
|
|
679
|
+
* (the approver lane, the park's sync-first decline, the durable-mandate source, a child's inherited seat) and the readers
|
|
680
|
+
* of the word ({@link deriveAskEffective}) cannot disagree about whether a person is there.
|
|
681
|
+
*/
|
|
682
|
+
export declare function resolveAskSeat(spec: Pick<TaskSpec, "onAsk" | "approvalReach">, deps: Pick<RunnerDeps, "onAsk">): AskSeat;
|
|
683
|
+
/** The form and provenance half of {@link resolveAskSeat} — the projection the manifest's two halves and a server's
|
|
684
|
+
* per-leg judgment read (hand it the request's `approvalReach` beside the seat). */
|
|
685
|
+
export declare function resolveAskSeamForm(spec: Pick<TaskSpec, "onAsk" | "approvalReach">, deps: Pick<RunnerDeps, "onAsk">): {
|
|
644
686
|
form: AskSeamForm;
|
|
645
687
|
provenance?: SeamProvenance;
|
|
646
688
|
};
|