@sema-agent/core 7.5.0 → 7.5.2
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 +44 -0
- package/dist/core/auto-mode.d.ts +9 -0
- package/dist/core/auto-mode.js +11 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/protocol-table.d.ts +5 -0
- package/dist/core/protocol-table.js +1 -0
- package/dist/core/runner/abort-race.d.ts +41 -0
- package/dist/core/runner/abort-race.js +38 -0
- package/dist/core/runner/checkpoint-scope.d.ts +44 -0
- package/dist/core/runner/checkpoint-scope.js +7 -0
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/content-ask-bindings.d.ts +27 -0
- package/dist/core/runner/content-ask-bindings.js +1 -0
- package/dist/core/runner/contracts.d.ts +1990 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +24 -1
- package/dist/core/runner/denial-limit-arms.js +21 -0
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
- package/dist/core/runner/inherited-ask-grants.js +29 -0
- package/dist/core/runner/park-commit.d.ts +108 -0
- package/dist/core/runner/park-commit.js +32 -0
- package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
- package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +110 -0
- package/dist/core/runner/prepare-ask-lane.js +133 -0
- package/dist/core/runner/prepare-boundary-parks.d.ts +105 -0
- package/dist/core/runner/prepare-boundary-parks.js +169 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-context-lane.d.ts +119 -0
- package/dist/core/runner/prepare-context-lane.js +230 -0
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +177 -0
- package/dist/core/runner/prepare-gate-stations.js +290 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +9 -11
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
- package/dist/core/runner/prepare-memory-engine-session.js +233 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-park-ask.d.ts +164 -0
- package/dist/core/runner/prepare-park-ask.js +377 -0
- package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
- package/dist/core/runner/prepare-policy-chain.js +584 -0
- package/dist/core/runner/prepare-project-context.d.ts +119 -0
- package/dist/core/runner/prepare-project-context.js +148 -0
- package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
- package/dist/core/runner/prepare-prompt-assembly.js +162 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +119 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +179 -0
- package/dist/core/runner/prepare-question-face.d.ts +101 -0
- package/dist/core/runner/prepare-question-face.js +84 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -7
- package/dist/core/runner/prepare-safety-scan.js +1 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
- package/dist/core/runner/prepare-suspend-saga.js +308 -0
- package/dist/core/runner/prepare-task.d.ts +14 -1976
- package/dist/core/runner/prepare-task.js +182 -5162
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
- package/dist/core/runner/prepare-turn-wiring.js +201 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +192 -0
- package/dist/core/runner/prepare-wiring-manifest.js +247 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -56
- package/dist/core/runner/prepare-workspace-restore.js +4 -38
- package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
- package/dist/core/runner/prompt-hash-salt.js +2 -0
- package/dist/core/runner/remote-env-retry.d.ts +29 -0
- package/dist/core/runner/remote-env-retry.js +16 -0
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +3 -0
- package/dist/core/tool-policy.d.ts +16 -0
- package/dist/core/tool-policy.js +3 -0
- package/dist/core/types.d.ts +2 -2
- package/dist/core/write-protect.js +3 -2
- package/package.json +6 -2
- /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
- /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
2
|
+
import { policyAskClassOf } from "../ask-class.js";
|
|
3
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
4
|
+
import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
5
|
+
import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js";
|
|
6
|
+
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
|
|
7
|
+
import { isIsolated } from "../remote-env.js";
|
|
8
|
+
import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, isLiveApproverSeat, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
9
|
+
import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
10
|
+
import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
|
|
11
|
+
import { emitTrace } from "../trace.js";
|
|
12
|
+
import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
13
|
+
import { headlessDenyAtFold, headlessDenyAtRecheck, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
|
|
14
|
+
import { askGrantShapeOf } from "./inherited-ask-grants.js";
|
|
15
|
+
import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
16
|
+
import { createSessionRulePolicy } from "./session-rule-policy.js";
|
|
17
|
+
export async function preparePolicyChain(input) {
|
|
18
|
+
const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedBash, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
|
|
19
|
+
const policy = lockedPreflight.toolPolicy;
|
|
20
|
+
const auditPolicyNames = (auditee) => {
|
|
21
|
+
const nameGroups = toolPolicyNameSets(auditee);
|
|
22
|
+
if (nameGroups.length > 0) {
|
|
23
|
+
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
24
|
+
for (const t of harnessTools)
|
|
25
|
+
known.add(t.name);
|
|
26
|
+
const unmatched = new Set();
|
|
27
|
+
for (const g of nameGroups) {
|
|
28
|
+
for (const [kind, list] of [["deny", g.deny], ["ask", g.ask], ["allow", g.allow]]) {
|
|
29
|
+
for (const n of list ?? []) {
|
|
30
|
+
if (known.has(n))
|
|
31
|
+
continue;
|
|
32
|
+
if (isNamespacedCoveringRuleName(n) && [...known].some((k) => namespacedRuleNameCovers(n, k)))
|
|
33
|
+
continue;
|
|
34
|
+
const retired = RETIRED_TOOL_NAMES.get(n);
|
|
35
|
+
if (retired !== undefined) {
|
|
36
|
+
const err = new Error(`tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${retired}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`);
|
|
37
|
+
err.code = "config.legacy_tool_name";
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
if (n.includes("__") && protocolOf(n) === undefined) {
|
|
41
|
+
const err = new Error(`tool policy ${kind}-list entry "${n}" carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`);
|
|
42
|
+
err.code = "config.legacy_tool_name";
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
unmatched.add(`"${n}" (${kind})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const dedupeKey = [...unmatched].sort().join("|");
|
|
50
|
+
const advised = advisedPolicyNames.get(deps) ?? new Set();
|
|
51
|
+
if (unmatched.size > 0 && !advised.has(dedupeKey)) {
|
|
52
|
+
advised.add(dedupeKey);
|
|
53
|
+
if (advised.size > ADVISED_KEYS_CAP) {
|
|
54
|
+
const oldest = advised.values().next().value;
|
|
55
|
+
if (oldest !== undefined)
|
|
56
|
+
advised.delete(oldest);
|
|
57
|
+
}
|
|
58
|
+
advisedPolicyNames.set(deps, advised);
|
|
59
|
+
try {
|
|
60
|
+
const e = new Error(`toolPolicy name audit: ${[...unmatched].join(", ")} match(es) no tool mounted this run (primary or alias) — the rule cannot fire here. Often a typo or a self-report/wire name skew (e.g. TodoWrite vs the mounted TaskCreate family; "*" has no wildcard meaning in a name-keyed policy). Advisory only: enforcement is unchanged, and a name can be legitimate for another scenario or a per-spawn tool.`);
|
|
61
|
+
e.code = "config.toolpolicy.unmatched_names";
|
|
62
|
+
deps.onError?.(e, { phase: "config", sessionId });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
auditPolicyNames(policy);
|
|
70
|
+
let sessionRulePolicy;
|
|
71
|
+
if (deps.sessionPolicyStore && spec.sessionId) {
|
|
72
|
+
const rules = await deps.sessionPolicyStore.getRules(spec.sessionId, spec.principal);
|
|
73
|
+
if (rules) {
|
|
74
|
+
sessionRulePolicy = createSessionRulePolicy(rules, { env: executionEnv, rootPath: taskRootFinal, toolEffects });
|
|
75
|
+
ownSessionRulesRef.current = {
|
|
76
|
+
sessionId: spec.sessionId,
|
|
77
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
78
|
+
rev: rules.rev,
|
|
79
|
+
rules,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const ancestorRulePolicies = [];
|
|
84
|
+
for (const snap of inheritedAncestorRules ?? []) {
|
|
85
|
+
let ancestorRules = snap.rules;
|
|
86
|
+
if (deps.sessionPolicyStore) {
|
|
87
|
+
try {
|
|
88
|
+
const live = await deps.sessionPolicyStore.getRules(snap.sessionId, snap.principal);
|
|
89
|
+
if (live !== null && live.rev >= snap.rev)
|
|
90
|
+
ancestorRules = live;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
ancestorRulePolicies.push(createSessionRulePolicy(ancestorRules, { env: executionEnv, rootPath: taskRootFinal, toolEffects }));
|
|
96
|
+
}
|
|
97
|
+
const skillScopePolicy = hasSkillManifest
|
|
98
|
+
? createActiveSkillScopePolicy({
|
|
99
|
+
scope: skillScope,
|
|
100
|
+
env: executionEnv,
|
|
101
|
+
rootPath: taskRootFinal,
|
|
102
|
+
toolEffects,
|
|
103
|
+
})
|
|
104
|
+
: undefined;
|
|
105
|
+
const frozenConstraintLayers = (seedInheritedGate?.constraintChain ?? [])
|
|
106
|
+
.filter((e) => !("opaque" in e))
|
|
107
|
+
.map((entry) => ({
|
|
108
|
+
check: (creq) => checkToolPolicyProjection(entry, creq) ?? { action: "allow" },
|
|
109
|
+
}));
|
|
110
|
+
const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, ...frozenConstraintLayers, skillScopePolicy].filter((p) => p !== undefined);
|
|
111
|
+
for (const layer of narrowingLayers)
|
|
112
|
+
auditPolicyNames(layer);
|
|
113
|
+
const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
|
|
114
|
+
const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy;
|
|
115
|
+
const hooks = spec.hooks ?? deps.hooks;
|
|
116
|
+
const hookTimeoutMs = resolveHookTimeoutMs(hooks?.timeoutMs, notifyOwnHookCrash, hooks);
|
|
117
|
+
const preToolUseObservational = hooks?.preToolUse !== undefined && hooks.preToolUseObservational === true;
|
|
118
|
+
const ownGatePreToolUse = !preToolUseObservational
|
|
119
|
+
? hooks?.preToolUse
|
|
120
|
+
: async (toolName, input, ctx) => {
|
|
121
|
+
const r = await hooks.preToolUse(toolName, cloneObserverInput(input), ctx);
|
|
122
|
+
if (r === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
try {
|
|
125
|
+
const action = typeof r.action === "string" ? r.action : "(non-string action)";
|
|
126
|
+
deps.onError?.(new Error(`a PreToolUse hook declared observational (Hooks.preToolUseObservational) returned a decision ("${action}") while screening ` +
|
|
127
|
+
`"${toolName}" — the decision was NOT adopted and the call proceeded as if the hook had no opinion. ` +
|
|
128
|
+
`A declared-observational face must return undefined; drop the declaration if its verdicts are meant to count ` +
|
|
129
|
+
`(they then also travel to delegated children as an inherited screening constraint).`), { phase: "hook", sessionId, classification: "observational-hook-verdict-ignored" });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
};
|
|
135
|
+
const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0);
|
|
136
|
+
const sharedFirstDecision = new Map();
|
|
137
|
+
const SHARED_FIRST_DECISION_CAP = 256;
|
|
138
|
+
const inheritedUnavailableAsks = new Set();
|
|
139
|
+
const INHERITED_UNAVAILABLE_CAP = 256;
|
|
140
|
+
const markInheritedUnavailable = (toolCallId) => {
|
|
141
|
+
if (inheritedUnavailableAsks.size >= INHERITED_UNAVAILABLE_CAP && !inheritedUnavailableAsks.has(toolCallId))
|
|
142
|
+
return false;
|
|
143
|
+
inheritedUnavailableAsks.add(toolCallId);
|
|
144
|
+
return true;
|
|
145
|
+
};
|
|
146
|
+
const inheritedAskGrants = new Map();
|
|
147
|
+
const recordInheritedAskGrant = (toolCallId, approver, presented, waitMs) => {
|
|
148
|
+
if (typeof approver !== "function")
|
|
149
|
+
return;
|
|
150
|
+
if (presented === undefined)
|
|
151
|
+
return;
|
|
152
|
+
if (inheritedAskGrants.size >= INHERITED_UNAVAILABLE_CAP && !inheritedAskGrants.has(toolCallId))
|
|
153
|
+
return;
|
|
154
|
+
const argsJson = askGrantShapeOf(presented);
|
|
155
|
+
if (argsJson === undefined)
|
|
156
|
+
return;
|
|
157
|
+
inheritedAskGrants.set(toolCallId, { approver, argsJson, presented, waitMs });
|
|
158
|
+
};
|
|
159
|
+
const sandboxDeclared = isIsolated(executionEnv);
|
|
160
|
+
const sandboxAdmissionArmed = (() => {
|
|
161
|
+
if (!sandboxDeclared)
|
|
162
|
+
return false;
|
|
163
|
+
const store = resolveCheckpointStore(spec, deps);
|
|
164
|
+
const missing = [];
|
|
165
|
+
if (store === undefined)
|
|
166
|
+
missing.push("a wired CheckpointStore");
|
|
167
|
+
else if (store.redecision?.reopen !== true)
|
|
168
|
+
missing.push("a CheckpointStore that DECLARES redecision.reopen (declaration, never duck-typing)");
|
|
169
|
+
if (spec.durableApproval === undefined && runtimeCaps?.forceDurableGate !== true)
|
|
170
|
+
missing.push("a durable park (spec.durableApproval, or the forceDurableGate entitlement)");
|
|
171
|
+
if (missing.length === 0)
|
|
172
|
+
return true;
|
|
173
|
+
try {
|
|
174
|
+
const e = new Error(`sandbox admission mode NOT armed: the execution env declares an isolated sandbox (capabilities.isolation), but the mode's ` +
|
|
175
|
+
`park prerequisites are missing — ${missing.join("; ")}. Asks keep the conservative default chain (no auto-admission, no boundary park).`);
|
|
176
|
+
e.code = "config.sandbox_admission_unavailable";
|
|
177
|
+
deps.onError?.(e, { phase: "config", sessionId, classification: "sandbox-admission-unavailable" });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
})();
|
|
183
|
+
const foldAskClasses = new Map();
|
|
184
|
+
const FOLD_ASK_CLASSES_CAP = 256;
|
|
185
|
+
const recordFoldAskClass = (toolCallId, cls, layer) => {
|
|
186
|
+
if (!foldAskClasses.has(toolCallId) && foldAskClasses.size >= FOLD_ASK_CLASSES_CAP)
|
|
187
|
+
return;
|
|
188
|
+
const list = foldAskClasses.get(toolCallId) ?? [];
|
|
189
|
+
list.push({ cls, layer });
|
|
190
|
+
foldAskClasses.set(toolCallId, list);
|
|
191
|
+
};
|
|
192
|
+
const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName) || toolName === SEND_MESSAGE_TOOL_NAME;
|
|
193
|
+
const emitSandboxAdmitted = (info) => {
|
|
194
|
+
emitTrace(deps.tracer, () => ({
|
|
195
|
+
kind: "permission.sandbox_admitted",
|
|
196
|
+
version: 1,
|
|
197
|
+
taskId: hostTaskId,
|
|
198
|
+
toolCallId: info.toolCallId,
|
|
199
|
+
toolName: info.toolName,
|
|
200
|
+
askClasses: info.askClasses.map((a) => a.cls),
|
|
201
|
+
sourceLayers: info.askClasses.map((a) => a.layer),
|
|
202
|
+
boundary: info.boundary,
|
|
203
|
+
ts: Date.now(),
|
|
204
|
+
}));
|
|
205
|
+
};
|
|
206
|
+
const ancestorSandboxAdmissions = new Map();
|
|
207
|
+
const recordAncestorSandboxAdmission = (toolCallId, toolName) => {
|
|
208
|
+
const list = ancestorSandboxAdmissions.get(toolCallId);
|
|
209
|
+
if (list !== undefined)
|
|
210
|
+
list.push({ cls: "sandbox_local", layer: "ancestor_constraint" });
|
|
211
|
+
else
|
|
212
|
+
ancestorSandboxAdmissions.set(toolCallId, [{ cls: "sandbox_local", layer: "ancestor_constraint" }]);
|
|
213
|
+
void toolName;
|
|
214
|
+
};
|
|
215
|
+
const callerPolicyLayer = policy !== undefined && sameInstanceAncestorCount > 0
|
|
216
|
+
? {
|
|
217
|
+
check: async (creq, csignal) => {
|
|
218
|
+
const decision = await policy.check(creq, csignal ?? abortController.signal);
|
|
219
|
+
sharedFirstDecision.set(creq.toolCallId, decision);
|
|
220
|
+
if (sharedFirstDecision.size > SHARED_FIRST_DECISION_CAP) {
|
|
221
|
+
const oldest = sharedFirstDecision.keys().next().value;
|
|
222
|
+
if (oldest !== undefined)
|
|
223
|
+
sharedFirstDecision.delete(oldest);
|
|
224
|
+
}
|
|
225
|
+
return decision;
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
: policy;
|
|
229
|
+
const askSourceIdentity = () => ({
|
|
230
|
+
principal: spec.principal,
|
|
231
|
+
sourceTaskId: sessionId,
|
|
232
|
+
...(internals?.parentTaskId !== undefined ? { fromSubagent: true } : {}),
|
|
233
|
+
...((internals?.explicitAgentName ?? internals?.agentName) !== undefined
|
|
234
|
+
? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
|
|
235
|
+
: {}),
|
|
236
|
+
...(delegation.isDelegatedChild ? { isDelegatedChild: true } : {}),
|
|
237
|
+
});
|
|
238
|
+
const riskAxesOf = (toolName) => {
|
|
239
|
+
const tier = irreversibilityTier.get(toolName);
|
|
240
|
+
const negatives = axisExplicitNegatives.get(toolName);
|
|
241
|
+
const irreversible = tier === "always" || tier === "maybe" ? true : tier === "never" ? false : negatives?.irreversible;
|
|
242
|
+
const egress = egressTools.has(toolName) ? true : negatives?.egress;
|
|
243
|
+
if (irreversible === undefined && egress === undefined)
|
|
244
|
+
return {};
|
|
245
|
+
return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
|
|
246
|
+
};
|
|
247
|
+
const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore);
|
|
248
|
+
const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({
|
|
249
|
+
provider: deps.permissionRuleStore,
|
|
250
|
+
localOwnerDeclared: deps.localOwnerRules === true,
|
|
251
|
+
principal: spec.principal,
|
|
252
|
+
sessionId,
|
|
253
|
+
root: taskRootFinal,
|
|
254
|
+
liveCwd: () => handsCwdRef?.current,
|
|
255
|
+
questionToolMounted,
|
|
256
|
+
questionToolName: ASK_USER_QUESTION_TOOL_NAME,
|
|
257
|
+
onRevisionDefect: (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }),
|
|
258
|
+
onDisclosure: (message) => emitTrace(deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: hostTaskId, message, ts: Date.now() })),
|
|
259
|
+
});
|
|
260
|
+
const ruleOffersOf = createRuleOffersOf({
|
|
261
|
+
laneArmed: permissionRuleLane !== undefined,
|
|
262
|
+
principal: spec.principal,
|
|
263
|
+
localOwnerDeclared: deps.localOwnerRules === true,
|
|
264
|
+
egressTools,
|
|
265
|
+
irreversibilityTier,
|
|
266
|
+
shellGatedBash,
|
|
267
|
+
taskRoot: taskRootFinal,
|
|
268
|
+
cwdRef: handsCwdRef,
|
|
269
|
+
deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined,
|
|
270
|
+
});
|
|
271
|
+
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider, ancestorTracker) => {
|
|
272
|
+
let editArgs = edit;
|
|
273
|
+
for (let round = 0;; round++) {
|
|
274
|
+
if (round >= 3) {
|
|
275
|
+
return {
|
|
276
|
+
action: "deny",
|
|
277
|
+
message: `the approval-edit chain for "${creq.toolName}" exceeded the re-approval cap (inherited parent policy) — denied fail-closed; re-submit the edited action directly`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
let re;
|
|
281
|
+
try {
|
|
282
|
+
re = await pol.check({ ...creq, args: editArgs }, csignal ?? abortController.signal);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
return {
|
|
286
|
+
action: "deny",
|
|
287
|
+
message: `inherited parent policy re-check of the approved edit errored for "${creq.toolName}": ${err instanceof Error ? err.message : String(err)}`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (re.action === "deny")
|
|
291
|
+
return re;
|
|
292
|
+
if (re.action === "allow") {
|
|
293
|
+
return { action: "allow", updatedInput: re.updatedInput !== undefined ? re.updatedInput : editArgs };
|
|
294
|
+
}
|
|
295
|
+
if (re.updatedInput !== undefined)
|
|
296
|
+
editArgs = re.updatedInput;
|
|
297
|
+
const judged = await judgeInheritedClassifier({ autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit" });
|
|
298
|
+
if (judged.kind === "allow")
|
|
299
|
+
return { action: "allow", updatedInput: editArgs };
|
|
300
|
+
if (judged.kind === "deny")
|
|
301
|
+
return judged.result;
|
|
302
|
+
const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere, origin: editOrigin } = judged;
|
|
303
|
+
re = editAsk;
|
|
304
|
+
const rr = await resolveAsk({
|
|
305
|
+
toolName: creq.toolName,
|
|
306
|
+
toolCallId: creq.toolCallId,
|
|
307
|
+
args: editArgs,
|
|
308
|
+
...ruleOffersOf(creq.toolName, editArgs, { ...(re ?? {}), ancestorResolved: true }),
|
|
309
|
+
message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
310
|
+
...askSourceIdentity(),
|
|
311
|
+
...riskAxesOf(creq.toolName),
|
|
312
|
+
...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
313
|
+
...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
|
|
314
|
+
...inheritedAskCarry(editOrigin, editFallback, isLiveApproverSeat(onAskOf), ancestorTracker),
|
|
315
|
+
ruleEvidence: inheritedAskEvidence,
|
|
316
|
+
}, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
317
|
+
settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
|
|
318
|
+
if (rr.action !== "allow")
|
|
319
|
+
return rr;
|
|
320
|
+
if (rr.updatedInput === undefined)
|
|
321
|
+
return { action: "allow", updatedInput: editArgs };
|
|
322
|
+
editArgs = rr.updatedInput;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const parentConstraintWrappers = (inheritedParentConstraints ?? []).map((pc) => pc.preToolUse !== undefined &&
|
|
326
|
+
pc.preToolUse === hooks?.preToolUse &&
|
|
327
|
+
!preToolUseObservational &&
|
|
328
|
+
pc.durableMandate !== true &&
|
|
329
|
+
pc.contentMandate !== true &&
|
|
330
|
+
askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
|
|
331
|
+
pc.hookEnv === hookEnvSource &&
|
|
332
|
+
pc.autoMode?.decider === autoModeDecider
|
|
333
|
+
? { check: () => ({ action: "allow" }) }
|
|
334
|
+
: policy !== undefined && pc.policy === policy
|
|
335
|
+
? {
|
|
336
|
+
check: async (creq, csignal) => {
|
|
337
|
+
const first = sharedFirstDecision.get(creq.toolCallId);
|
|
338
|
+
if (first === undefined) {
|
|
339
|
+
return {
|
|
340
|
+
action: "deny",
|
|
341
|
+
message: `inherited parent policy could not be arbitrated for "${creq.toolName}" ` +
|
|
342
|
+
`(shared-instance first decision unavailable); denied fail-closed`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (first.action === "deny")
|
|
346
|
+
return first;
|
|
347
|
+
if (first.action === "allow")
|
|
348
|
+
return { action: "allow" };
|
|
349
|
+
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
350
|
+
if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
|
|
351
|
+
return first;
|
|
352
|
+
return {
|
|
353
|
+
action: "deny",
|
|
354
|
+
message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
|
|
355
|
+
`denied fail-closed (tighten-only)`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (pc.durableMandate === true) {
|
|
359
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
360
|
+
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
361
|
+
markInheritedUnavailable(creq.toolCallId)) {
|
|
362
|
+
return first;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
action: "deny",
|
|
366
|
+
message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
|
|
367
|
+
`ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
|
|
371
|
+
if (judged.kind === "allow")
|
|
372
|
+
return { action: "allow" };
|
|
373
|
+
if (judged.kind === "deny")
|
|
374
|
+
return judged.result;
|
|
375
|
+
const { ask: inheritedAsk, fallback, mintedHere, origin: inheritedOrigin } = judged;
|
|
376
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
377
|
+
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
378
|
+
return { action: "allow" };
|
|
379
|
+
}
|
|
380
|
+
const presentedArgs = creq.args;
|
|
381
|
+
const askT0 = now();
|
|
382
|
+
const resolved = await resolveAsk({
|
|
383
|
+
toolName: creq.toolName,
|
|
384
|
+
toolCallId: creq.toolCallId,
|
|
385
|
+
args: presentedArgs,
|
|
386
|
+
...ruleOffersOf(creq.toolName, presentedArgs, { ...(inheritedAsk.action === "ask" ? inheritedAsk : {}), ancestorResolved: true }),
|
|
387
|
+
message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
388
|
+
...askSourceIdentity(),
|
|
389
|
+
...riskAxesOf(creq.toolName),
|
|
390
|
+
...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
391
|
+
...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
|
|
392
|
+
...inheritedAskCarry(inheritedOrigin, fallback, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
|
|
393
|
+
ruleEvidence: inheritedAskEvidence,
|
|
394
|
+
}, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
395
|
+
const askWaitMs = Math.max(0, now() - askT0);
|
|
396
|
+
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
397
|
+
if (markInheritedUnavailable(creq.toolCallId))
|
|
398
|
+
return inheritedAsk;
|
|
399
|
+
return {
|
|
400
|
+
action: "deny",
|
|
401
|
+
message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
|
|
405
|
+
if (resolved.action !== "allow")
|
|
406
|
+
return resolved;
|
|
407
|
+
if (resolved.updatedInput !== undefined) {
|
|
408
|
+
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
|
|
409
|
+
}
|
|
410
|
+
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
|
|
411
|
+
return { action: "allow" };
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
: {
|
|
415
|
+
check: async (creq, csignal) => {
|
|
416
|
+
let decision;
|
|
417
|
+
try {
|
|
418
|
+
decision = await pc.policy.check(creq, csignal ?? abortController.signal);
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
return {
|
|
422
|
+
action: "deny",
|
|
423
|
+
message: `inherited parent policy errored for "${creq.toolName}": ${err instanceof Error ? err.message : String(err)}`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (decision.action !== "ask")
|
|
427
|
+
return decision;
|
|
428
|
+
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
429
|
+
if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
|
|
430
|
+
return decision;
|
|
431
|
+
return {
|
|
432
|
+
action: "deny",
|
|
433
|
+
message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
|
|
434
|
+
`denied fail-closed (tighten-only)`,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
if (pc.durableMandate === true) {
|
|
438
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
439
|
+
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
440
|
+
markInheritedUnavailable(creq.toolCallId)) {
|
|
441
|
+
return decision;
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
action: "deny",
|
|
445
|
+
message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
|
|
446
|
+
`ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
|
|
450
|
+
const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call" });
|
|
451
|
+
if (judged.kind === "allow")
|
|
452
|
+
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
453
|
+
if (judged.kind === "deny")
|
|
454
|
+
return judged.result;
|
|
455
|
+
const { ask: inheritedAsk, fallback, mintedHere, origin: inheritedOrigin } = judged;
|
|
456
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
457
|
+
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
458
|
+
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
459
|
+
}
|
|
460
|
+
const askT0 = now();
|
|
461
|
+
const resolved = await resolveAsk({
|
|
462
|
+
toolName: creq.toolName,
|
|
463
|
+
toolCallId: creq.toolCallId,
|
|
464
|
+
args: presentedArgs,
|
|
465
|
+
...ruleOffersOf(creq.toolName, presentedArgs, { ...(inheritedAsk.action === "ask" ? inheritedAsk : {}), ancestorResolved: true }),
|
|
466
|
+
message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
467
|
+
...askSourceIdentity(),
|
|
468
|
+
...riskAxesOf(creq.toolName),
|
|
469
|
+
...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
470
|
+
...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
|
|
471
|
+
...inheritedAskCarry(inheritedOrigin, fallback, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
|
|
472
|
+
ruleEvidence: inheritedAskEvidence,
|
|
473
|
+
}, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
474
|
+
const askWaitMs = Math.max(0, now() - askT0);
|
|
475
|
+
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
476
|
+
if (markInheritedUnavailable(creq.toolCallId))
|
|
477
|
+
return inheritedAsk;
|
|
478
|
+
return {
|
|
479
|
+
action: "deny",
|
|
480
|
+
message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
|
|
484
|
+
if (resolved.action !== "allow")
|
|
485
|
+
return resolved;
|
|
486
|
+
if (resolved.updatedInput !== undefined) {
|
|
487
|
+
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
|
|
488
|
+
}
|
|
489
|
+
if (pc.preToolUse === undefined) {
|
|
490
|
+
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
|
|
491
|
+
}
|
|
492
|
+
return decision.updatedInput !== undefined ? { ...resolved, updatedInput: decision.updatedInput } : resolved;
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
const rewriteCapableLayers = policy !== undefined || parentConstraintWrappers.length > 0;
|
|
496
|
+
const rewriteEmitterIndex = new Map();
|
|
497
|
+
const rewriteCapableChain = [
|
|
498
|
+
...(callerPolicyLayer !== undefined ? [callerPolicyLayer] : []),
|
|
499
|
+
...parentConstraintWrappers,
|
|
500
|
+
];
|
|
501
|
+
const callerSlotOffset = callerPolicyLayer !== undefined ? 1 : 0;
|
|
502
|
+
const sameInstanceAdapterIndices = new Set();
|
|
503
|
+
(inheritedParentConstraints ?? []).forEach((pc, j) => {
|
|
504
|
+
if (policy !== undefined && pc.policy === policy)
|
|
505
|
+
sameInstanceAdapterIndices.add(j + callerSlotOffset);
|
|
506
|
+
});
|
|
507
|
+
const instrumentedChain = rewriteCapableChain.map((layer, idx) => ({
|
|
508
|
+
check: async (creq, csignal) => {
|
|
509
|
+
const d = await layer.check(creq, csignal);
|
|
510
|
+
if (d.updatedInput !== undefined)
|
|
511
|
+
rewriteEmitterIndex.set(creq.toolCallId, idx);
|
|
512
|
+
if (d.action === "ask") {
|
|
513
|
+
recordFoldAskClass(creq.toolCallId, idx < callerSlotOffset ? policyAskClassOf(policy) : "mandate", idx < callerSlotOffset ? "caller_policy" : "ancestor_constraint");
|
|
514
|
+
}
|
|
515
|
+
return d;
|
|
516
|
+
},
|
|
517
|
+
}));
|
|
518
|
+
const policyLayers = [
|
|
519
|
+
...narrowingLayers,
|
|
520
|
+
...instrumentedChain,
|
|
521
|
+
...(rewriteCapableLayers ? narrowingLayers : []),
|
|
522
|
+
];
|
|
523
|
+
const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy()];
|
|
524
|
+
const guardedPolicyLayers = rewriteCapableLayers
|
|
525
|
+
? [...integrityGuardLayers, ...policyLayers, ...integrityGuardLayers]
|
|
526
|
+
: [...integrityGuardLayers, ...policyLayers];
|
|
527
|
+
const foldedPolicy = guardedPolicyLayers.length === 1 ? guardedPolicyLayers[0] : combinePolicies(...guardedPolicyLayers);
|
|
528
|
+
const effectivePolicy = foldedPolicy !== undefined && rewriteCapableLayers
|
|
529
|
+
? {
|
|
530
|
+
check: async (req, signal) => {
|
|
531
|
+
let decision;
|
|
532
|
+
try {
|
|
533
|
+
decision = await foldedPolicy.check(req, signal);
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
rewriteEmitterIndex.delete(req.toolCallId);
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
const emitterIdx = rewriteEmitterIndex.get(req.toolCallId);
|
|
540
|
+
rewriteEmitterIndex.delete(req.toolCallId);
|
|
541
|
+
if (decision.action === "deny" || decision.updatedInput === undefined)
|
|
542
|
+
return decision;
|
|
543
|
+
const recheckPrefix = rewriteCapableChain.slice(0, emitterIdx ?? rewriteCapableChain.length);
|
|
544
|
+
const postEmitterSameInstanceAdapters = [];
|
|
545
|
+
if (emitterIdx !== undefined && emitterIdx > 0) {
|
|
546
|
+
for (let i = emitterIdx + 1; i < rewriteCapableChain.length; i++) {
|
|
547
|
+
if (sameInstanceAdapterIndices.has(i))
|
|
548
|
+
postEmitterSameInstanceAdapters.push(rewriteCapableChain[i]);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const recheckLayers = [...recheckPrefix, ...postEmitterSameInstanceAdapters];
|
|
552
|
+
const finalReq = { ...req, args: decision.updatedInput };
|
|
553
|
+
let asked;
|
|
554
|
+
try {
|
|
555
|
+
for (const layer of recheckLayers) {
|
|
556
|
+
const d = await layer.check(finalReq, signal);
|
|
557
|
+
if (d.action === "deny") {
|
|
558
|
+
return { ...d, updatedInput: decision.updatedInput };
|
|
559
|
+
}
|
|
560
|
+
if (d.updatedInput !== undefined) {
|
|
561
|
+
return {
|
|
562
|
+
action: "deny",
|
|
563
|
+
message: "policy required a rewrite during final-form re-check — the arguments that would execute were not accepted as-is (fail-closed; rewrite chains are not iterated to a fixpoint)",
|
|
564
|
+
updatedInput: decision.updatedInput,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (d.action === "ask" && asked === undefined)
|
|
568
|
+
asked = d;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
rewriteEmitterIndex.delete(req.toolCallId);
|
|
573
|
+
}
|
|
574
|
+
if (asked !== undefined && decision.action === "allow") {
|
|
575
|
+
return { ...asked, updatedInput: decision.updatedInput };
|
|
576
|
+
}
|
|
577
|
+
return decision;
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
: foldedPolicy;
|
|
581
|
+
return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions };
|
|
582
|
+
}
|
|
583
|
+
const advisedPolicyNames = new WeakMap();
|
|
584
|
+
const ADVISED_KEYS_CAP = 64;
|