@sema-agent/core 7.41.0 → 7.42.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 +90 -0
- package/dist/agents/subagent.js +2 -0
- package/dist/core/agent-definition.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +241 -0
- package/dist/core/allow-entry.js +340 -0
- package/dist/core/allow-rule-shadow.d.ts +18 -0
- package/dist/core/allow-rule-shadow.js +1 -0
- package/dist/core/ask-origin.d.ts +5 -5
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/auto-mode.d.ts +3 -2
- package/dist/core/checkpoint-store.d.ts +11 -19
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/decision-receipt.d.ts +4 -2
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +8 -5
- package/dist/core/gate-fold.js +1 -1
- package/dist/core/gate-lanes.d.ts +1 -49
- package/dist/core/gate-lanes.js +85 -223
- package/dist/core/governance-codes.d.ts +3 -3
- package/dist/core/governance-codes.js +3 -3
- package/dist/core/hooks.d.ts +13 -10
- package/dist/core/hooks.js +5 -3
- package/dist/core/mode-policy.d.ts +3 -3
- package/dist/core/mode-policy.js +2 -2
- package/dist/core/permission-rules.d.ts +24 -23
- package/dist/core/permission-rules.js +37 -33
- package/dist/core/reminder-disclosure.d.ts +53 -2
- package/dist/core/reminder-disclosure.js +49 -36
- package/dist/core/rule-lane-reads.d.ts +84 -0
- package/dist/core/rule-lane-reads.js +164 -0
- package/dist/core/runner/contracts.d.ts +4 -2
- package/dist/core/runner/gate-exit.d.ts +3 -3
- package/dist/core/runner/image.js +4 -3
- package/dist/core/runner/inline-image.d.ts +14 -0
- package/dist/core/runner/inline-image.js +26 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
- package/dist/core/runner/permission-rule-lanes.js +1 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -19
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +12 -0
- package/dist/core/runner/prepare-policy-chain.js +10 -9
- package/dist/core/runner/prepare-project-context.d.ts +13 -3
- package/dist/core/runner/prepare-project-context.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -5
- package/dist/core/runner/prepare-turn-wiring.d.ts +16 -3
- package/dist/core/runner/prepare-turn-wiring.js +19 -18
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner-deps.d.ts +11 -0
- package/dist/core/skill-grant.d.ts +8 -47
- package/dist/core/skill-grant.js +15 -136
- package/dist/core/synthetic-read-verdict.d.ts +108 -0
- package/dist/core/synthetic-read-verdict.js +111 -0
- package/dist/core/task-spec.d.ts +18 -0
- package/dist/core/tool-name-readings.d.ts +1 -1
- package/dist/core/tool-name-readings.js +1 -1
- package/dist/core/tool-policy.d.ts +25 -24
- package/dist/core/tool-policy.js +26 -17
- package/dist/core/tool-spec.d.ts +7 -0
- package/dist/core/trace.d.ts +16 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -0
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +69 -0
- package/dist/tools/fs/dir-entries.js +60 -0
- package/dist/tools/fs/fs-pdf.d.ts +7 -11
- package/dist/tools/fs/fs-pdf.js +5 -14
- package/dist/tools/fs/fs-read.d.ts +173 -2
- package/dist/tools/fs/fs-read.js +399 -286
- package/dist/tools/fs/image-admission.d.ts +51 -0
- package/dist/tools/fs/image-admission.js +36 -0
- package/dist/tools/fs/search.js +3 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +29 -1
- package/wire-changes/core@7.41.1.json +159 -0
- package/wire-changes/core@7.42.0.json +1107 -0
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText,
|
|
2
|
-
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence,
|
|
1
|
+
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
|
+
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
3
3
|
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
4
|
import { classifierUnavailableArm } from "./classifier-unavailable-arm.js";
|
|
5
5
|
import { SETTLEMENT_NOBODY_JUDGED } from "./gate-outcome.js";
|
|
6
6
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
7
|
-
import { isRuleBehavior } from "./permission-rule-model.js";
|
|
8
7
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
9
8
|
import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arms.js";
|
|
10
9
|
import { skillAllowDecision, skillRowRefusal } from "./skill-grant.js";
|
|
10
|
+
import { allowRuleShadowOf } from "./allow-entry.js";
|
|
11
11
|
import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
12
12
|
import { readShellCommand } from "./shell-lexer.js";
|
|
13
13
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
@@ -15,146 +15,13 @@ import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
|
15
15
|
import { callFaceSeat } from "./tool-roster.js";
|
|
16
16
|
import { heldBy, wireOf } from "./tool-name-readings.js";
|
|
17
17
|
import { asksUnstated, modeAcceptsFileCommand, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
|
|
18
|
-
import {
|
|
18
|
+
import { ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON } from "./permission-rule-org.js";
|
|
19
19
|
import { exitGate } from "./runner/gate-exit.js";
|
|
20
|
+
import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
|
|
20
21
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
21
22
|
import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
|
|
22
23
|
import { thrownText } from "./thrown-value.js";
|
|
23
24
|
const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
|
|
24
|
-
function isPlainOwnRecord(x) {
|
|
25
|
-
try {
|
|
26
|
-
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
27
|
-
return false;
|
|
28
|
-
const p = Object.getPrototypeOf(x);
|
|
29
|
-
return p === Object.prototype || p === null;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function ownDataValue(obj, key) {
|
|
36
|
-
try {
|
|
37
|
-
const d = Object.getOwnPropertyDescriptor(obj, key);
|
|
38
|
-
return d && "value" in d ? d.value : undefined;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
export function normalizeOrgGateVerdict(answer, unreadable) {
|
|
45
|
-
const clean = (v) => Object.assign(Object.create(null), v);
|
|
46
|
-
const readable = (x) => {
|
|
47
|
-
try {
|
|
48
|
-
return x !== null && (typeof x === "object" || typeof x === "function") && !Array.isArray(x);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
if (!readable(answer))
|
|
55
|
-
return clean({ status: "unavailable", disclosures: [unreadable] });
|
|
56
|
-
const status = ownDataValue(answer, "status");
|
|
57
|
-
if (status === "unavailable") {
|
|
58
|
-
const disclosures = ownDataValue(answer, "disclosures");
|
|
59
|
-
let lines = [];
|
|
60
|
-
if (Array.isArray(disclosures)) {
|
|
61
|
-
try {
|
|
62
|
-
for (let i = 0; i < disclosures.length; i++) {
|
|
63
|
-
const d = disclosures[i];
|
|
64
|
-
if (typeof d === "string")
|
|
65
|
-
lines.push(d);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
lines = [];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return clean({ status: "unavailable", disclosures: lines.length > 0 ? lines : [unreadable] });
|
|
73
|
-
}
|
|
74
|
-
if (status !== "available")
|
|
75
|
-
return clean({ status: "unavailable", disclosures: [unreadable] });
|
|
76
|
-
const revision = ownDataValue(answer, "revision");
|
|
77
|
-
const revisionCell = typeof revision === "number" && Number.isFinite(revision) ? { revision } : {};
|
|
78
|
-
const verdict = ownDataValue(answer, "verdict");
|
|
79
|
-
if (verdict === undefined)
|
|
80
|
-
return clean({ status: "available", ...revisionCell });
|
|
81
|
-
if (!readable(verdict))
|
|
82
|
-
return clean({ status: "unavailable", disclosures: [unreadable] });
|
|
83
|
-
const behavior = ownDataValue(verdict, "behavior");
|
|
84
|
-
const rule = ownDataValue(verdict, "rule");
|
|
85
|
-
if ((behavior !== "deny" && behavior !== "ask") || typeof rule !== "string")
|
|
86
|
-
return clean({ status: "unavailable", disclosures: [unreadable] });
|
|
87
|
-
const reason = ownDataValue(verdict, "unreadable");
|
|
88
|
-
const unreadableCell = behavior === "ask" && typeof reason === "string" ? { unreadable: reason } : {};
|
|
89
|
-
return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell });
|
|
90
|
-
}
|
|
91
|
-
function copiedUsableDots(dots) {
|
|
92
|
-
let copied;
|
|
93
|
-
try {
|
|
94
|
-
if (Array.isArray(dots)) {
|
|
95
|
-
const walked = [];
|
|
96
|
-
for (let i = 0; i < dots.length; i++) {
|
|
97
|
-
const d = dots[i];
|
|
98
|
-
walked.push(isPlainOwnRecord(d) ? { actor: ownDataValue(d, "actor"), counter: ownDataValue(d, "counter") } : { actor: undefined, counter: undefined });
|
|
99
|
-
}
|
|
100
|
-
copied = walked;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
copied = undefined;
|
|
105
|
-
}
|
|
106
|
-
const usable = copied?.every((d) => typeof d.actor === "string" && typeof d.counter === "number" && Number.isFinite(d.counter)) === true && copied.length > 0;
|
|
107
|
-
return usable ? { dots: copied } : {};
|
|
108
|
-
}
|
|
109
|
-
export function normalizePersistedRuleHit(hit) {
|
|
110
|
-
if (!isPlainOwnRecord(hit))
|
|
111
|
-
return {};
|
|
112
|
-
if (ownDataValue(hit, "unreadable") === true) {
|
|
113
|
-
const reason = ownDataValue(hit, "reason");
|
|
114
|
-
return typeof reason === "string" ? { unreadable: true, reason } : { unreadable: true };
|
|
115
|
-
}
|
|
116
|
-
const rulesRaw = ownDataValue(hit, "rules");
|
|
117
|
-
if (rulesRaw === undefined) {
|
|
118
|
-
const coverageRaw = ownDataValue(hit, "segmentCoverage");
|
|
119
|
-
let rows;
|
|
120
|
-
try {
|
|
121
|
-
if (Array.isArray(coverageRaw)) {
|
|
122
|
-
const walked = [];
|
|
123
|
-
for (let i = 0; i < coverageRaw.length; i++) {
|
|
124
|
-
const r = coverageRaw[i];
|
|
125
|
-
walked.push(isPlainOwnRecord(r) ? { segment: ownDataValue(r, "segment"), covered: ownDataValue(r, "covered") } : { segment: undefined, covered: undefined });
|
|
126
|
-
}
|
|
127
|
-
rows = walked.every((r) => typeof r.segment === "string" && typeof r.covered === "boolean") ? walked : undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
rows = undefined;
|
|
132
|
-
}
|
|
133
|
-
return rows !== undefined && rows.length > 0 ? { coverage: rows.map((r) => ({ segment: r.segment, covered: r.covered })) } : {};
|
|
134
|
-
}
|
|
135
|
-
let members;
|
|
136
|
-
try {
|
|
137
|
-
if (Array.isArray(rulesRaw)) {
|
|
138
|
-
const walked = [];
|
|
139
|
-
for (let i = 0; i < rulesRaw.length; i++) {
|
|
140
|
-
const m = rulesRaw[i];
|
|
141
|
-
walked.push(isPlainOwnRecord(m) ? { rule: ownDataValue(m, "rule"), dots: ownDataValue(m, "dots") } : { rule: undefined, dots: undefined });
|
|
142
|
-
}
|
|
143
|
-
members = walked;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
members = undefined;
|
|
148
|
-
}
|
|
149
|
-
if (members === undefined || members.length === 0)
|
|
150
|
-
return {};
|
|
151
|
-
if (!members.every((m) => typeof m.rule === "string"))
|
|
152
|
-
return {};
|
|
153
|
-
const behavior = ownDataValue(hit, "behavior");
|
|
154
|
-
if (!isRuleBehavior(behavior))
|
|
155
|
-
return {};
|
|
156
|
-
return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
|
|
157
|
-
}
|
|
158
25
|
function parkWith(suspendAsk, parkArgs, carry) {
|
|
159
26
|
return suspendAsk(...parkArgs, carry);
|
|
160
27
|
}
|
|
@@ -200,21 +67,7 @@ export async function runGateLanes(pass) {
|
|
|
200
67
|
if (wireOf("lanes.orgContentAskExemption", pass.req) === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
|
|
201
68
|
return { decision: current, org: undefined };
|
|
202
69
|
let decided = current;
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }));
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
|
|
209
|
-
}
|
|
210
|
-
const raw = await settleOrgVerdictWithin(pending.catch((err) => ({
|
|
211
|
-
status: "unavailable",
|
|
212
|
-
disclosures: [`the org adjudication face threw: ${describeThrown(err)}`],
|
|
213
|
-
})), {
|
|
214
|
-
status: "unavailable",
|
|
215
|
-
disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`],
|
|
216
|
-
}, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
217
|
-
const answer = normalizeOrgGateVerdict(raw, "the org adjudication face answered something this gate cannot read as a verdict — treated as unreadable governance, never as an empty one");
|
|
70
|
+
const answer = await readOrgRuleLane(input.orgRules, { toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }, input.abortSignal);
|
|
218
71
|
if (answer.status === "unavailable") {
|
|
219
72
|
orgEvidence = { revisionAbsent: "unavailable", ruleAbsent: "unavailable" };
|
|
220
73
|
orgRealApprovalRequired = true;
|
|
@@ -262,16 +115,7 @@ export async function runGateLanes(pass) {
|
|
|
262
115
|
const orgAnswer = await applyOrgLayer(pass.decision, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput);
|
|
263
116
|
pass.decision = orgAnswer.decision;
|
|
264
117
|
const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
|
|
265
|
-
const readPersistedRules =
|
|
266
|
-
let pendingHit;
|
|
267
|
-
try {
|
|
268
|
-
pendingHit = Promise.resolve(input.persistedRules?.adjudicate(req));
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
return { unreadable: true };
|
|
272
|
-
}
|
|
273
|
-
return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
274
|
-
};
|
|
118
|
+
const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
|
|
275
119
|
const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
|
|
276
120
|
const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
|
|
277
121
|
{
|
|
@@ -294,81 +138,101 @@ export async function runGateLanes(pass) {
|
|
|
294
138
|
pass.currentInput = pass.policyRewrite;
|
|
295
139
|
pass.req.args = pass.policyRewrite;
|
|
296
140
|
}
|
|
297
|
-
const persistedRuleMandate = persistedRuleMandateOf({
|
|
298
|
-
...mandateMarksNow(),
|
|
299
|
-
probeMandated: pass.decision.action === "ask" && pass.decision.probeMandated === true,
|
|
300
|
-
});
|
|
301
141
|
const ruleMayClear = () => ruleClearanceOf(pass.decision.action === "ask" ? pass.decision : undefined, mandateMarksNow()).ruleMayClear;
|
|
142
|
+
const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer))).row.ruleMayClear;
|
|
302
143
|
let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
|
|
303
144
|
if (laneAnswer?.unreadable === true)
|
|
304
145
|
personalEvidence = { dotsAbsent: "unavailable" };
|
|
305
146
|
let laneCoverage;
|
|
147
|
+
const questionTool = wireOf("lanes.allowLayerQuestionSkip", pass.req) === ASK_USER_QUESTION_TOOL_NAME;
|
|
306
148
|
const allowLayerMayClear = () => !orgRealApprovalRequired &&
|
|
307
149
|
pass.decision.action === "ask" &&
|
|
308
150
|
pass.decision.requiresRealApproval !== true &&
|
|
309
151
|
pass.decision.decisionReason !== "hook" &&
|
|
310
|
-
|
|
152
|
+
!questionTool &&
|
|
311
153
|
!markedUnresolvable();
|
|
154
|
+
const unionDotsOf = (rules) => {
|
|
155
|
+
const seen = new Set();
|
|
156
|
+
const union = [];
|
|
157
|
+
for (const r of rules) {
|
|
158
|
+
if (r.dots === undefined || r.dots.length === 0)
|
|
159
|
+
return { dotsAbsent: "not_reported" };
|
|
160
|
+
for (const d of r.dots) {
|
|
161
|
+
const key = JSON.stringify([d.actor, d.counter]);
|
|
162
|
+
if (!seen.has(key))
|
|
163
|
+
(seen.add(key), union.push(d));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { dots: union };
|
|
167
|
+
};
|
|
312
168
|
if (input.persistedRules && allowLayerMayClear()) {
|
|
313
169
|
const answer = laneAnswer ?? {};
|
|
314
|
-
const hitRules = answer.hit?.behavior === "allow" ? answer.hit.rules : undefined;
|
|
315
170
|
laneCoverage = answer.coverage;
|
|
316
|
-
personalEvidence = { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
171
|
+
personalEvidence = answer.hit?.behavior === "ask" ? unionDotsOf(answer.hit.rules) : { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
|
|
172
|
+
}
|
|
173
|
+
const afterRuleClear = () => {
|
|
174
|
+
if (input.permissionMode === undefined || input.permissionMode() === "auto")
|
|
175
|
+
input.autoMode?.denialTracking?.recordAllow();
|
|
176
|
+
};
|
|
177
|
+
const mode = input.permissionMode?.();
|
|
178
|
+
const rewrite = pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {};
|
|
179
|
+
const listAllow = (decisionReason, whose, entries) => ({
|
|
180
|
+
action: "allow",
|
|
181
|
+
message: `${whose} allowedTools (${entries.map((e) => inlineUntrusted(e, 200)).join(", ")}) ${entries.length === 1 ? "covers" : "cover"} this call`,
|
|
182
|
+
decisionReason,
|
|
183
|
+
...rewrite,
|
|
184
|
+
});
|
|
185
|
+
const storedAllows = laneAnswer?.hit?.behavior === "allow" ? laneAnswer.hit.rules : undefined;
|
|
186
|
+
const members = [
|
|
187
|
+
["hook", () => (pass.hookAllowed === true && pass.policyRewrite === undefined ? { entries: [], clear: () => ({ action: "allow", message: "a PreToolUse hook allowed this call", decisionReason: "hook" }), afterClear: async () => undefined } : undefined)],
|
|
188
|
+
["deployment", () => {
|
|
189
|
+
const hit = input.deploymentAllows?.allowing(pass.req, mode);
|
|
190
|
+
return hit && { entries: hit.entries, clear: () => listAllow("deployment_rule", "the deployment's", hit.entries), afterClear: () => notifier.notifyAsync(() => input.deploymentAllows?.onAllowed?.({ toolName: pass.req.toolName, toolCallId, entries: hit.entries }), "toolGate.allowedToolsResolved") };
|
|
191
|
+
}],
|
|
192
|
+
["persisted", () => storedAllows && {
|
|
193
|
+
entries: storedAllows.map((r) => r.rule),
|
|
194
|
+
clear: () => ({ action: "allow", message: storedAllows.length === 1 ? `a persisted allow rule (${disclosedRuleSet(storedAllows)}) covers this call` : `persisted allow rules (${disclosedRuleSet(storedAllows)}) cover every segment of this call`, decisionReason: "persisted_rule", ...rewrite }),
|
|
195
|
+
afterClear: () => notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: pass.req.toolName, toolCallId, rules: storedAllows.map((r) => r.rule) }), "toolGate.persistedRuleResolved"),
|
|
196
|
+
shadowed: () => (personalEvidence = unionDotsOf(storedAllows)),
|
|
197
|
+
}],
|
|
198
|
+
["skill", () => {
|
|
199
|
+
const hit = input.skillGrants?.allowing(pass.req, mode);
|
|
200
|
+
return hit && { entries: hit.entries, clear: () => skillAllowDecision(hit, pass.policyRewrite), afterClear: () => notifier.notifyAsync(() => input.skillGrants?.onAllowed?.({ toolName: pass.req.toolName, toolCallId, ...hit }), "toolGate.skillGrantResolved") };
|
|
201
|
+
}],
|
|
202
|
+
["request", () => {
|
|
203
|
+
const hit = input.requestAllows?.allowing(pass.req, mode);
|
|
204
|
+
return hit && { entries: hit.entries, clear: () => listAllow("request_rule", "this run's", hit.entries), afterClear: () => notifier.notifyAsync(() => input.requestAllows?.onAllowed?.({ toolName: pass.req.toolName, toolCallId, entries: hit.entries }), "toolGate.allowedToolsResolved") };
|
|
205
|
+
}],
|
|
206
|
+
];
|
|
207
|
+
const shadows = [];
|
|
208
|
+
if (!questionTool) {
|
|
209
|
+
for (const [member, match] of members) {
|
|
210
|
+
if (pass.decision.action !== "ask")
|
|
211
|
+
break;
|
|
212
|
+
const hit = match();
|
|
213
|
+
if (hit === undefined)
|
|
214
|
+
continue;
|
|
215
|
+
if (allowLayerMayClear() && ruleMayClear() && classMayClear()) {
|
|
216
|
+
pass.decision = hit.clear();
|
|
217
|
+
await hit.afterClear();
|
|
218
|
+
if (member !== "hook")
|
|
219
|
+
afterRuleClear();
|
|
220
|
+
break;
|
|
330
221
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (answer.hit?.behavior === "ask")
|
|
334
|
-
personalEvidence = unionDotsOf(answer.hit.rules);
|
|
335
|
-
if (hitRules !== undefined && ruleMayClear()) {
|
|
336
|
-
pass.decision = {
|
|
337
|
-
action: "allow",
|
|
338
|
-
message: hitRules.length === 1
|
|
339
|
-
? `a persisted allow rule (${disclosedRuleSet(hitRules)}) covers this call`
|
|
340
|
-
: `persisted allow rules (${disclosedRuleSet(hitRules)}) cover every segment of this call`,
|
|
341
|
-
decisionReason: "persisted_rule",
|
|
342
|
-
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
343
|
-
};
|
|
344
|
-
await notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: pass.req.toolName, toolCallId, rules: hitRules.map((r) => r.rule) }), "toolGate.persistedRuleResolved");
|
|
345
|
-
}
|
|
346
|
-
else if (hitRules !== undefined) {
|
|
347
|
-
personalEvidence = unionDotsOf(hitRules);
|
|
348
|
-
const shownRule = disclosedRuleSet(hitRules);
|
|
349
|
-
const mandateNoun = persistedRuleMandate === undefined && pass.decision.matchedAskRule === undefined
|
|
350
|
-
? "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"
|
|
351
|
-
: mandateDisclosureOf(persistedRuleMandate);
|
|
352
|
-
pass.decision = {
|
|
353
|
-
...pass.decision,
|
|
354
|
-
persistedRuleShadowed: shownRule,
|
|
355
|
-
message: hitRules.length === 1
|
|
356
|
-
? `${pass.decision.message !== undefined ? `${pass.decision.message} ` : ""}(a persisted allow rule (${shownRule}) matches this call but does not clear the ask — ${mandateNoun})`
|
|
357
|
-
: `${pass.decision.message !== undefined ? `${pass.decision.message} ` : ""}(persisted allow rules (${shownRule}) match this call but do not clear the ask — ${mandateNoun})`,
|
|
358
|
-
};
|
|
222
|
+
shadows.push(allowRuleShadowOf(member, hit.entries));
|
|
223
|
+
hit.shadowed?.();
|
|
359
224
|
}
|
|
360
225
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
pass.decision =
|
|
364
|
-
await notifier.notifyAsync(() => input.skillGrants?.onAllowed?.({ toolName: pass.req.toolName, toolCallId, ...skillHit }), "toolGate.skillGrantResolved");
|
|
226
|
+
if (pass.decision.action === "ask") {
|
|
227
|
+
const { allowRuleShadowed: _claimed, ...standing } = pass.decision;
|
|
228
|
+
pass.decision = shadows.length > 0 ? { ...standing, allowRuleShadowed: shadows } : standing;
|
|
365
229
|
}
|
|
366
230
|
const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
|
|
367
231
|
if (pass.decision.action === "ask") {
|
|
368
232
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
369
233
|
pass.decision = stampAskOrigin(stamped, originFacts, classifyStanding(stamped, originFacts));
|
|
370
234
|
}
|
|
371
|
-
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
235
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && classMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
372
236
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
373
237
|
const args = pass.req.args;
|
|
374
238
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
@@ -382,6 +246,7 @@ export async function runGateLanes(pass) {
|
|
|
382
246
|
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
383
247
|
};
|
|
384
248
|
await notifier.notifyAsync(() => input.onReadOnlyAllowed?.({ toolName: pass.req.toolName, toolCallId, command }), "toolGate.readOnlyAllowed");
|
|
249
|
+
afterRuleClear();
|
|
385
250
|
}
|
|
386
251
|
else if (shape !== undefined && pass.tightenedBy === "shell_gate" && changesDirectoryBeforeGit(shape, input.trackedCwd?.())) {
|
|
387
252
|
pass.decision = { ...pass.decision, message: CD_GIT_COMPOUND_SENTENCE };
|
|
@@ -530,7 +395,7 @@ export async function runGateLanes(pass) {
|
|
|
530
395
|
...(d.action === "ask" && d.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
531
396
|
});
|
|
532
397
|
if (suspendAsk && pass.decision.action === "ask") {
|
|
533
|
-
const parkArgs = [pass.req, pass.currentInput, safety, undefined, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.
|
|
398
|
+
const parkArgs = [pass.req, pass.currentInput, safety, undefined, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.allowRuleShadowed : undefined, pass.decision.action === "ask" ? pass.decision.decisionReason : undefined, pass.decision.action === "ask" ? pass.decision.probeReason : undefined, pass.decision.action === "ask" ? pass.decision.probeCause : undefined, pass.decision.action === "ask" ? pass.decision.segmentCoverage : undefined, pass.decision.action === "ask" ? pass.decision.matchedAskRule : undefined, pass.decision.action === "ask" ? pass.decision.probeMandated : undefined];
|
|
534
399
|
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(pass.decision));
|
|
535
400
|
if (suspended) {
|
|
536
401
|
if ("parkFailed" in suspended)
|
|
@@ -550,7 +415,7 @@ export async function runGateLanes(pass) {
|
|
|
550
415
|
pass.req.args = outcome.presentedInput;
|
|
551
416
|
}
|
|
552
417
|
if (suspendAsk && outcome.parkDeclined && pass.parkFailed === undefined) {
|
|
553
|
-
const parkArgs = [pass.req, pass.currentInput, safety, true, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.
|
|
418
|
+
const parkArgs = [pass.req, pass.currentInput, safety, true, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.allowRuleShadowed : undefined, pass.decision.action === "ask" ? pass.decision.decisionReason : undefined, pass.decision.action === "ask" ? pass.decision.probeReason : undefined, pass.decision.action === "ask" ? pass.decision.probeCause : undefined, pass.decision.action === "ask" ? pass.decision.segmentCoverage : undefined, pass.decision.action === "ask" ? pass.decision.matchedAskRule : undefined, pass.decision.action === "ask" ? pass.decision.probeMandated : undefined];
|
|
554
419
|
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(pass.decision));
|
|
555
420
|
if (suspended) {
|
|
556
421
|
if ("parkFailed" in suspended)
|
|
@@ -586,7 +451,7 @@ export async function runGateLanes(pass) {
|
|
|
586
451
|
pass.deniedBy = "ask_resolution";
|
|
587
452
|
pass.decision = resolved;
|
|
588
453
|
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && pass.parkFailed === undefined) {
|
|
589
|
-
const parkArgs = [pass.req, pass.currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.
|
|
454
|
+
const parkArgs = [pass.req, pass.currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.allowRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeMandated : undefined];
|
|
590
455
|
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(askBeforeResolve));
|
|
591
456
|
if (suspended) {
|
|
592
457
|
if ("parkFailed" in suspended)
|
|
@@ -613,7 +478,6 @@ export async function runGateLanes(pass) {
|
|
|
613
478
|
let editArgs = pass.decision.updatedInput;
|
|
614
479
|
let editDenied;
|
|
615
480
|
let editRewrittenSinceHuman = false;
|
|
616
|
-
let editHookAllowed = false;
|
|
617
481
|
for (let round = 0;; round++) {
|
|
618
482
|
if (round >= 3) {
|
|
619
483
|
editDenied = {
|
|
@@ -623,7 +487,6 @@ export async function runGateLanes(pass) {
|
|
|
623
487
|
pass.deniedBy = "policy";
|
|
624
488
|
break;
|
|
625
489
|
}
|
|
626
|
-
editHookAllowed = false;
|
|
627
490
|
if (screening !== undefined) {
|
|
628
491
|
let hr;
|
|
629
492
|
let hrUnanswered;
|
|
@@ -665,7 +528,6 @@ export async function runGateLanes(pass) {
|
|
|
665
528
|
editArgs = hr.updatedInput;
|
|
666
529
|
editRewrittenSinceHuman = true;
|
|
667
530
|
}
|
|
668
|
-
editHookAllowed = hr.action === "allow";
|
|
669
531
|
}
|
|
670
532
|
}
|
|
671
533
|
let recheck = { action: "allow" };
|
|
@@ -727,7 +589,7 @@ export async function runGateLanes(pass) {
|
|
|
727
589
|
editArgs = recheck.updatedInput;
|
|
728
590
|
editRewrittenSinceHuman = true;
|
|
729
591
|
}
|
|
730
|
-
if (!editRewrittenSinceHuman || !asksUnstated(recheck, permissionSeatAt(input, editArgs)?.seat, input.permissionMode?.()
|
|
592
|
+
if (!editRewrittenSinceHuman || !asksUnstated(recheck, permissionSeatAt(input, editArgs)?.seat, input.permissionMode?.()))
|
|
731
593
|
break;
|
|
732
594
|
recheck = { action: "ask", message: permissionSeatAt(input, editArgs)?.question ?? unstatedAskMessage(toolName) };
|
|
733
595
|
pass.tightenedBy = "unstated";
|
|
@@ -136,7 +136,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
136
136
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
137
137
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
138
138
|
*/
|
|
139
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "
|
|
139
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.allowed_tool_not_granted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable"];
|
|
140
140
|
/** A catalogued code (see {@link ENGINE_NOTICE_CODES}); {@link NOTICE_MINTER} says who mints it. NOT the type of
|
|
141
141
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
142
142
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -181,6 +181,7 @@ declare const NOTICE_AUDIENCE_TABLE: {
|
|
|
181
181
|
readonly "permission.ask_released_by_mode": "user";
|
|
182
182
|
readonly "permission.read_deny_unapplied": "user";
|
|
183
183
|
readonly "permission.rule_store_unavailable": "user";
|
|
184
|
+
readonly "permission.allowed_tool_not_granted": "user";
|
|
184
185
|
readonly "memory.capture_opted_out": "user";
|
|
185
186
|
readonly "memory.capture_optout_unpersisted": "user";
|
|
186
187
|
readonly "memory.consolidation_withheld": "operator";
|
|
@@ -233,7 +234,6 @@ declare const NOTICE_AUDIENCE_TABLE: {
|
|
|
233
234
|
readonly "route.tool_call_id_folded": "operator";
|
|
234
235
|
readonly "context.tool_turn_repaired": "operator";
|
|
235
236
|
readonly "skills.listing_truncated": "operator";
|
|
236
|
-
readonly "skills.allowed_tool_not_granted": "operator";
|
|
237
237
|
readonly "agents.listing_truncated": "operator";
|
|
238
238
|
readonly "tool_result.offload_put_failed": "operator";
|
|
239
239
|
readonly "config.forward_sink_failed": "operator";
|
|
@@ -294,6 +294,7 @@ declare const NOTICE_MINTER_TABLE: {
|
|
|
294
294
|
readonly "permission.ask_released_by_mode": "engine";
|
|
295
295
|
readonly "permission.read_deny_unapplied": "engine";
|
|
296
296
|
readonly "permission.rule_store_unavailable": "engine";
|
|
297
|
+
readonly "permission.allowed_tool_not_granted": "engine";
|
|
297
298
|
readonly "peer.inbound_disposition": "engine";
|
|
298
299
|
readonly "peer.held_settled": "engine";
|
|
299
300
|
readonly "peer.idle_subscription": "engine";
|
|
@@ -344,7 +345,6 @@ declare const NOTICE_MINTER_TABLE: {
|
|
|
344
345
|
readonly "memory.capture_opted_out": "engine";
|
|
345
346
|
readonly "memory.capture_optout_unpersisted": "engine";
|
|
346
347
|
readonly "skills.listing_truncated": "engine";
|
|
347
|
-
readonly "skills.allowed_tool_not_granted": "engine";
|
|
348
348
|
readonly "agents.listing_truncated": "engine";
|
|
349
349
|
readonly "tool_result.offload_put_failed": "engine";
|
|
350
350
|
readonly "instructions.source_changed": "server";
|
|
@@ -143,6 +143,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
143
143
|
"permission.ask_released_by_mode",
|
|
144
144
|
"permission.read_deny_unapplied",
|
|
145
145
|
"permission.rule_store_unavailable",
|
|
146
|
+
"permission.allowed_tool_not_granted",
|
|
146
147
|
"peer.inbound_disposition",
|
|
147
148
|
"peer.held_settled",
|
|
148
149
|
"peer.idle_subscription",
|
|
@@ -193,7 +194,6 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
193
194
|
"memory.capture_opted_out",
|
|
194
195
|
"memory.capture_optout_unpersisted",
|
|
195
196
|
"skills.listing_truncated",
|
|
196
|
-
"skills.allowed_tool_not_granted",
|
|
197
197
|
"agents.listing_truncated",
|
|
198
198
|
"tool_result.offload_put_failed",
|
|
199
199
|
"instructions.source_changed",
|
|
@@ -228,6 +228,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
228
228
|
"permission.ask_released_by_mode": "user",
|
|
229
229
|
"permission.read_deny_unapplied": "user",
|
|
230
230
|
"permission.rule_store_unavailable": "user",
|
|
231
|
+
"permission.allowed_tool_not_granted": "user",
|
|
231
232
|
"memory.capture_opted_out": "user",
|
|
232
233
|
"memory.capture_optout_unpersisted": "user",
|
|
233
234
|
"memory.consolidation_withheld": "operator",
|
|
@@ -280,7 +281,6 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
280
281
|
"route.tool_call_id_folded": "operator",
|
|
281
282
|
"context.tool_turn_repaired": "operator",
|
|
282
283
|
"skills.listing_truncated": "operator",
|
|
283
|
-
"skills.allowed_tool_not_granted": "operator",
|
|
284
284
|
"agents.listing_truncated": "operator",
|
|
285
285
|
"tool_result.offload_put_failed": "operator",
|
|
286
286
|
"config.forward_sink_failed": "operator",
|
|
@@ -323,6 +323,7 @@ const NOTICE_MINTER_TABLE = {
|
|
|
323
323
|
"permission.ask_released_by_mode": "engine",
|
|
324
324
|
"permission.read_deny_unapplied": "engine",
|
|
325
325
|
"permission.rule_store_unavailable": "engine",
|
|
326
|
+
"permission.allowed_tool_not_granted": "engine",
|
|
326
327
|
"peer.inbound_disposition": "engine",
|
|
327
328
|
"peer.held_settled": "engine",
|
|
328
329
|
"peer.idle_subscription": "engine",
|
|
@@ -373,7 +374,6 @@ const NOTICE_MINTER_TABLE = {
|
|
|
373
374
|
"memory.capture_opted_out": "engine",
|
|
374
375
|
"memory.capture_optout_unpersisted": "engine",
|
|
375
376
|
"skills.listing_truncated": "engine",
|
|
376
|
-
"skills.allowed_tool_not_granted": "engine",
|
|
377
377
|
"agents.listing_truncated": "engine",
|
|
378
378
|
"tool_result.offload_put_failed": "engine",
|
|
379
379
|
"instructions.source_changed": "server",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../inter
|
|
|
3
3
|
import type { DecisionReason, PermissionResult, ResolvedAsk, ToolCallFace, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
|
|
4
4
|
import type { GateOutcome } from "./gate-outcome.js";
|
|
5
5
|
import { type PreToolUseScreening } from "./runner/gate-exit.js";
|
|
6
|
-
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./
|
|
6
|
+
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
|
|
7
7
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
8
8
|
import { type AskClass } from "./ask-class.js";
|
|
9
9
|
import { type PermissionMode } from "./ask-origin.js";
|
|
@@ -13,6 +13,7 @@ import type { WiringLegKind } from "./wiring-manifest.js";
|
|
|
13
13
|
import { type SafeNotifier } from "./safe-notify.js";
|
|
14
14
|
import type { PermissionSeatInputs } from "./mode-policy.js";
|
|
15
15
|
import type { SkillGateSeats } from "./skill-grant.js";
|
|
16
|
+
import type { AllowEntryGateSeats } from "./allow-entry.js";
|
|
16
17
|
/**
|
|
17
18
|
* In-process hook seam (design/37) — a provider-agnostic interception layer modeled on CC's hooks,
|
|
18
19
|
* kept process-internal (no shell/HTTP executors, no settings files). The original tool-call trio:
|
|
@@ -834,10 +835,10 @@ export interface HookToolOutput {
|
|
|
834
835
|
* `updatedInput` rewrite) plus optional `additionalContext` injected into the eventual tool result.
|
|
835
836
|
*
|
|
836
837
|
* **The provenance members are not hook-settable inputs** (#174). `decisionReason` is the ENGINE's account
|
|
837
|
-
* of which layer produced a verdict, and `
|
|
838
|
-
*
|
|
839
|
-
* result is DISCARDED (a hook ask always reads `"hook"` downstream), and
|
|
840
|
-
* dropped rather than rendered on an approval card as
|
|
838
|
+
* of which layer produced a verdict, and `allowRuleShadowed` is the allow layer's own disclosure that a
|
|
839
|
+
* member matched without clearing the ask. The gate stamps both itself — a `decisionReason` on a hook
|
|
840
|
+
* result is DISCARDED (a hook ask always reads `"hook"` downstream), and an `allowRuleShadowed` is
|
|
841
|
+
* dropped rather than rendered on an approval card as an entry no member matched.
|
|
841
842
|
*
|
|
842
843
|
* Which of a hook's OTHER members reach the surviving decision depends on which fold arm ran, so read
|
|
843
844
|
* the arm, not a single list: an ask promoted from a policy `allow` is rebuilt from the hook's `message`,
|
|
@@ -1132,7 +1133,7 @@ export type PersistedRuleAnswer = PersistedRuleHit | PersistedRuleCoverage | Per
|
|
|
1132
1133
|
* signal; when the caller also supplies {@link ToolGateInput.callSignal}, the Runner's closures
|
|
1133
1134
|
* additionally bind their waits to that per-call signal (`AbortSignal.any` of the two), so a turn
|
|
1134
1135
|
* interrupt releases a pending ask instead of leaving it hanging on the run's lifetime alone. */
|
|
1135
|
-
export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats {
|
|
1136
|
+
export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, AllowEntryGateSeats {
|
|
1136
1137
|
/** RB-473 batch 2b — bounded disclosure for a HOST callback that throws inside this gate call's
|
|
1137
1138
|
* isolation scope (onHookError itself, the deny observer, the durable-park observer). The runner
|
|
1138
1139
|
* wires this to an `observer.notify_failed` trace event; absent = counted only (unchanged). */
|
|
@@ -1250,9 +1251,9 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats {
|
|
|
1250
1251
|
* ASK rule, from the org-unavailable tighten (whose resume semantics differ — see
|
|
1251
1252
|
* {@link import("./checkpoint-store.js").RealApprovalGateBit}), or from a policy/hook. */
|
|
1252
1253
|
realApproval?: import("./checkpoint-store.js").RealApprovalGateBit,
|
|
1253
|
-
/** The
|
|
1254
|
+
/** The allow-layer members that matched and could not clear (the surviving ask's `allowRuleShadowed`)
|
|
1254
1255
|
* — threaded so the park mint's risk descriptor carries the disclosure on the durable route. */
|
|
1255
|
-
|
|
1256
|
+
allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[],
|
|
1256
1257
|
/** The surviving ask's `decisionReason` — threaded so the park mint's rule-suggestion door can
|
|
1257
1258
|
* read the ask's origin on the durable route exactly as the synchronous mints read it off the
|
|
1258
1259
|
* decision (a hook-raised ask is one no persisted rule may clear, so its parked card must not
|
|
@@ -1561,7 +1562,7 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats {
|
|
|
1561
1562
|
* · #144 (ruled): a MANDATED ask is never consumed — an operator's shellGate:"always" tier and a
|
|
1562
1563
|
* tool's own egress/irreversibility marks are structural requirements, not classifier hesitation
|
|
1563
1564
|
* ("allow rules silence the classifier's questions, never a mandated one"). When a rule matches
|
|
1564
|
-
* but cannot clear, the surviving ask discloses it (
|
|
1565
|
+
* but cannot clear, the surviving ask discloses it (`allowRuleShadowed`, member `persisted`).
|
|
1565
1566
|
* · #502: an ask the call's own reversibility probe declared STRUCTURAL (`probeMandated`) joins that
|
|
1566
1567
|
* family. It is the one PER-CALL member of it, and the reason there has to be one: the built-in
|
|
1567
1568
|
* shell probe raises it for a read outside the session's allowed directories, which is a boundary
|
|
@@ -1800,7 +1801,9 @@ export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
|
1800
1801
|
export declare function createPreToolUseScreening(preToolUse: Hooks["preToolUse"] | undefined, input: Pick<ToolGateInput, "event" | "trackedCwd" | "hookEnv" | "identity" | "onHookError" | "abortSignal">, hookSeatMs: number, notifier: SafeNotifier): PreToolUseScreening | undefined;
|
|
1801
1802
|
/**
|
|
1802
1803
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
|
1803
|
-
* structural
|
|
1804
|
+
* structural: a hook's `allow` passes no deny, no ask rule and no mandated question; it is one member of the
|
|
1805
|
+
* allow layer and clears only the questions an allow rule may clear (a deployment that wants a question put to a
|
|
1806
|
+
* person whatever a hook says marks it `authoredBy: "deployment"` or `requiresRealApproval`):
|
|
1804
1807
|
*
|
|
1805
1808
|
* 1. **collect** — run the PreToolUse hook, threading any `updatedInput` rewrite into `currentInput`
|
|
1806
1809
|
* and collecting `additionalContext`. A hook `deny` short-circuits to a block immediately; a hook
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { decisionText, describeThrown, receiveHookResult, isEngineRefusal } from "./tool-policy.js";
|
|
1
|
+
import { abortedSeatDeny, decisionText, describeThrown, receiveHookResult, isEngineRefusal } from "./tool-policy.js";
|
|
2
2
|
import { asThrownError } from "./thrown-value.js";
|
|
3
3
|
import { createSettlementLedger, engineFailClosedExit, hookDenyExit } from "./runner/gate-exit.js";
|
|
4
4
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
5
5
|
import { runGateFold } from "./gate-fold.js";
|
|
6
6
|
import { runGateLanes } from "./gate-lanes.js";
|
|
7
|
-
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./
|
|
7
|
+
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
|
|
8
8
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
9
9
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
10
10
|
import { isAskOrigin, screenAskClassMembers } from "./ask-origin.js";
|
|
@@ -194,7 +194,9 @@ function screenPreToolUseResult(r) {
|
|
|
194
194
|
}
|
|
195
195
|
export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash, timeoutMs) {
|
|
196
196
|
return brandPolicyAskClass({
|
|
197
|
-
check: async (req) => {
|
|
197
|
+
check: async (req, signal) => {
|
|
198
|
+
if (signal?.aborted)
|
|
199
|
+
return abortedSeatDeny(req, `the inherited PreToolUse screening of "${req.toolName}" was not consulted: the run or turn was already interrupted`, "hook");
|
|
198
200
|
let r;
|
|
199
201
|
const boundMs = resolveHookTimeoutMs(timeoutMs, onCrash);
|
|
200
202
|
try {
|