UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

674 lines (673 loc) • 44.8 kB
import { policyAskClassOf } from "../ask-class.js"; import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js"; import { classifierUnavailableSeat } from "./classifier-ladder.js"; import { resolveCheckpointStore } from "../checkpoint-store.js"; import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js"; import { assertHookSeatsCallable } from "../hook-seats.js"; import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js"; import { isIsolated } from "../remote-env.js"; import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, wrapToolPolicy, attestAskUnresolvableCause } from "../tool-policy.js"; import { judgeRuleToolName } from "../tool-name-aliases.js"; import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js"; import { emitTrace } from "../trace.js"; import { autoStrippedRule, cardDisclosureOf } from "../allow-entry.js"; import { arbitrateByMode, askClassMembersOf, askMandateMember } from "../ask-origin.js"; import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js"; const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true }); import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, inheritedAskFallbackMessage, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js"; import { settleReadRootGrant } from "./read-root-grant.js"; import { askGrantShapeOf } from "./inherited-ask-grants.js"; import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js"; import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-policy.js"; import { thrownText } from "../thrown-value.js"; import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js"; export async function preparePolicyChain(input) { const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input; const ancestorArbitrations = new Map(); const arbitrateInherited = (ask, toolCallId, judged) => { const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) }); if (verdict.kind === "ask" || verdict.kind === "accepted") return undefined; ancestorArbitrations.set(toolCallId, verdict); if (verdict.kind === "released") return { action: "allow", message: `released by the ${verdict.mode} permission mode at an inherited ancestor layer (the ${verdict.askClass} ask was not put to anyone)`, decisionReason: "mode", ...(ask.updatedInput !== undefined ? { updatedInput: ask.updatedInput } : {}) }; return { action: "deny", message: `refused by the ${verdict.mode} permission mode at an inherited ancestor layer (${verdict.askClass})`, decisionReason: "mode" }; }; const policy = lockedPreflight.toolPolicy; const auditPolicyNames = (auditee) => { const nameGroups = toolPolicyNameSets(auditee); if (nameGroups.length > 0) { const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])])); for (const t of harnessTools) known.add(t.name); const unmatched = new Set(); for (const g of nameGroups) { for (const [kind, list] of [["deny", g.deny], ["ask", g.ask], ["allow", g.allow]]) { for (const n of list ?? []) { const verdict = judgeRuleToolName(n, known); if (verdict.kind !== "current") { const err = new Error(verdict.kind === "retired" ? `tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${verdict.guidance}) 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.` : `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.`); err.code = "config.legacy_tool_name"; throw err; } if (known.has(n)) continue; if (isNamespacedCoveringRuleName(n) && [...known].some((k) => namespacedRuleNameCovers(n, k))) continue; unmatched.add(`"${n}" (${kind})`); } } } const dedupeKey = [...unmatched].sort().join("|"); const advised = advisedPolicyNames.get(deps) ?? new Set(); if (unmatched.size > 0 && !advised.has(dedupeKey)) { advised.add(dedupeKey); if (advised.size > ADVISED_KEYS_CAP) { const oldest = advised.values().next().value; if (oldest !== undefined) advised.delete(oldest); } advisedPolicyNames.set(deps, advised); try { 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.`); e.code = "config.toolpolicy.unmatched_names"; deps.onError?.(e, { phase: "config", sessionId }); } catch { } } } }; auditPolicyNames(policy); const announceSessionRulesUnreadable = (reason) => { try { const e = new Error(`session permission rules: ${reason} — the snapshot this leg already enforces stays in force (a failed read never loosens it)`); e.code = "config.session_rules_unreadable"; deps.onError?.(e, { phase: "config", sessionId }); } catch { } }; let sessionRulePolicy; if (deps.sessionPolicyStore && spec.sessionId) { const store = deps.sessionPolicyStore; const sessionRuleKey = { sessionId: spec.sessionId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) }; const rules = await store.getRules(spec.sessionId, spec.principal); const seed = rules ?? { rev: 0 }; const publishOwnRules = (row) => { ownSessionRulesRef.current = { sessionId: sessionRuleKey.sessionId, ...(sessionRuleKey.principal !== undefined ? { principal: sessionRuleKey.principal } : {}), rev: row.rev, ...(row.gen !== undefined ? { gen: row.gen } : {}), rules: row, }; }; sessionRulePolicy = createSessionRulePolicy({ seed, read: createLiveSessionRules(store, { ...sessionRuleKey, seed }, { onUnreadable: announceSessionRulesUnreadable, onEnforced: publishOwnRules }) }, { env: executionEnv, rootPath: taskRootFinal, toolEffects }); if (rules) publishOwnRules(rules); } const ancestorRulePolicies = []; for (const [position, snap] of (inheritedAncestorRules ?? []).entries()) { const seed = { ...snap.rules, rev: snap.rev, ...(snap.gen !== undefined ? { gen: snap.gen } : {}) }; const publishAncestorRules = (row) => { ancestorSessionRulesRef.current.set(position, { sessionId: snap.sessionId, ...(snap.principal !== undefined ? { principal: snap.principal } : {}), rev: row.rev, ...(row.gen !== undefined ? { gen: row.gen } : {}), rules: row, }); }; ancestorRulePolicies.push(createSessionRulePolicy({ seed, ...(deps.sessionPolicyStore ? { read: createLiveSessionRules(deps.sessionPolicyStore, { sessionId: snap.sessionId, ...(snap.principal !== undefined ? { principal: snap.principal } : {}), seed }, { onUnreadable: announceSessionRulesUnreadable, onEnforced: publishAncestorRules }) } : {}), }, { env: executionEnv, rootPath: taskRootFinal, toolEffects })); } const frozenConstraintLayers = (seedInheritedGate?.constraintChain ?? []) .filter((e) => !("opaque" in e)) .map((entry) => ({ check: (creq) => checkToolPolicyProjection(entry, creq) ?? { action: "allow" }, })); const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, ...frozenConstraintLayers].filter((p) => p !== undefined); for (const layer of narrowingLayers) auditPolicyNames(layer); const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers); const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy; const hooks = spec.hooks ?? deps.hooks; assertHookSeatsCallable(hooks, spec.hooks !== undefined ? "spec" : "deps"); const hookTimeoutMs = resolveHookTimeoutMs(hooks?.timeoutMs, notifyOwnHookCrash, hooks); const preToolUseObservational = hooks?.preToolUse !== undefined && hooks.preToolUseObservational === true; const ownGatePreToolUse = !preToolUseObservational ? hooks?.preToolUse : async (toolName, input, ctx) => { const r = await hooks.preToolUse(toolName, cloneObserverInput(input), ctx); if (r === undefined) return undefined; try { const action = typeof r.action === "string" ? r.action : "(non-string action)"; deps.onError?.(new Error(`a PreToolUse hook declared observational (Hooks.preToolUseObservational) returned a decision ("${action}") while screening ` + `"${toolName}" — the decision was NOT adopted and the call proceeded as if the hook had no opinion. ` + `A declared-observational face must return undefined; drop the declaration if its verdicts are meant to count ` + `(they then also travel to delegated children as an inherited screening constraint).`), { phase: "hook", sessionId, classification: "observational-hook-verdict-ignored" }); } catch { } return undefined; }; const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0); const sharedFirstDecision = new Map(); const SHARED_FIRST_DECISION_CAP = 256; const inheritedUnavailableAsks = new Set(); const INHERITED_UNAVAILABLE_CAP = 256; const markInheritedUnavailable = (toolCallId) => { if (inheritedUnavailableAsks.size >= INHERITED_UNAVAILABLE_CAP && !inheritedUnavailableAsks.has(toolCallId)) return false; inheritedUnavailableAsks.add(toolCallId); return true; }; const mandatedCallFloats = (toolCallId) => resolveCheckpointStore(spec, deps) !== undefined && durableParkOptInDeclared({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined }) && markInheritedUnavailable(toolCallId); const mandateUnreconstructibleDeny = (creq) => attestAskUnresolvableCause({ action: "deny", message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`, }, "mandate_unreconstructible", creq); const markerChannelFullDeny = (creq) => attestAskUnresolvableCause({ action: "deny", message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed` }, "approver_unavailable", creq); const inheritedAskGrants = new Map(); const recordInheritedAskGrant = (toolCallId, approver, presented, waitMs, settled, classes) => { if (typeof approver !== "function") return; if (presented === undefined) return; if (inheritedAskGrants.size >= INHERITED_UNAVAILABLE_CAP && !inheritedAskGrants.has(toolCallId)) return; const argsJson = askGrantShapeOf(presented); if (argsJson === undefined) return; inheritedAskGrants.set(toolCallId, { approver, argsJson, presented, waitMs, settled, classes }); }; const sandboxDeclared = isIsolated(executionEnv); const sandboxAdmissionArmed = (() => { if (!sandboxDeclared) return false; const store = resolveCheckpointStore(spec, deps); const missing = []; if (store === undefined) missing.push("a wired CheckpointStore"); else if (store.redecision?.reopen !== true) missing.push("a CheckpointStore that DECLARES redecision.reopen (declaration, never duck-typing)"); if (spec.durableApproval === undefined && runtimeCaps?.forceDurableGate !== true) missing.push("a durable park (spec.durableApproval, or the forceDurableGate entitlement)"); if (missing.length === 0) return true; try { const e = new Error(`sandbox admission mode NOT armed: the execution env declares an isolated sandbox (capabilities.isolation), but the mode's ` + `park prerequisites are missing — ${missing.join("; ")}. Asks keep the conservative default chain (no auto-admission, no boundary park).`); e.code = "config.sandbox_admission_unavailable"; deps.onError?.(e, { phase: "config", sessionId, classification: "sandbox-admission-unavailable" }); } catch { } return false; })(); const foldAskClasses = new Map(); const FOLD_ASK_CLASSES_CAP = 256; const recordFoldAskClass = (toolCallId, cls, layer) => { if (!foldAskClasses.has(toolCallId) && foldAskClasses.size >= FOLD_ASK_CLASSES_CAP) return; const list = foldAskClasses.get(toolCallId) ?? []; list.push({ cls, layer }); foldAskClasses.set(toolCallId, list); }; const isDelegatedQuestion = (creq) => wireOf("inherited.questionFloat", creq) === ASK_USER_QUESTION_TOOL_NAME; const sandboxBoundaryCapable = (call) => heldBy("gate.sandboxBoundary.egress", egressTools, call) || !envHandToolNames.has(wireOf("gate.sandboxBoundary.hand", call)); const emitSandboxAdmitted = (info) => { emitTrace(deps.tracer, () => ({ kind: "permission.sandbox_admitted", version: 1, taskId: hostTaskId, toolCallId: info.toolCallId, toolName: info.toolName, askClasses: info.askClasses.map((a) => a.cls), sourceLayers: info.askClasses.map((a) => a.layer), boundary: info.boundary, ts: Date.now(), })); }; const ancestorSandboxAdmissions = new Map(); const recordAncestorSandboxAdmission = (toolCallId, toolName) => { const list = ancestorSandboxAdmissions.get(toolCallId); if (list !== undefined) list.push({ cls: "sandbox_local", layer: "ancestor_constraint" }); else ancestorSandboxAdmissions.set(toolCallId, [{ cls: "sandbox_local", layer: "ancestor_constraint" }]); void toolName; }; const callerPolicyLayer = policy !== undefined && sameInstanceAncestorCount > 0 ? wrapToolPolicy(policy, async (creq, csignal) => { const decision = refuseOutOfContractDecision(await policy.check(creq, csignal ?? abortController.signal)); sharedFirstDecision.set(creq.toolCallId, decision); if (sharedFirstDecision.size > SHARED_FIRST_DECISION_CAP) { const oldest = sharedFirstDecision.keys().next().value; if (oldest !== undefined) sharedFirstDecision.delete(oldest); } return decision; }) : policy; const askSourceIdentity = () => ({ principal: spec.principal, sourceTaskId: sessionId, ...(internals?.parentTaskId !== undefined ? { fromSubagent: true } : {}), ...((internals?.explicitAgentName ?? internals?.agentName) !== undefined ? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName } : {}), ...(delegation.isDelegatedChild ? { isDelegatedChild: true } : {}), }); const seatMarksOf = (call) => { const tierSeat = seatOf("report.riskAxes", irreversibilityTier, call); return { tier: tierSeat?.value, egress: heldBy("report.riskAxes.egress", egressTools, call), shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true, sources: new Set(heldValues("report.markSources", toolMarkSources, call).flatMap((s) => [...s])), }; }; const riskAxesOf = (call) => { const seat = seatMarksOf(call); const negatives = seatOf("report.riskAxes.negatives", axisExplicitNegatives, call)?.value; const irreversible = seat.tier === "always" || seat.tier === "maybe" ? true : seat.tier === "never" ? false : negatives?.irreversible; const egress = seat.egress ? true : negatives?.egress; if (irreversible === undefined && egress === undefined) return {}; return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } }; }; const inheritedStationFacts = (call) => { const seat = seatMarksOf(call); return { ...INHERITED_STATION_FACTS, mandateMarks: { egress: seat.egress, shellGated: false, ...(seat.tier === "always" && !seat.shellGated ? { irreversibility: "always" } : {}) }, ...(seat.sources.size > 0 ? { toolMarkSources: seat.sources } : {}), ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), }; }; const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore); const listFacts = { root: taskRootFinal, sessionId, liveCwd: () => handsCwdRef?.current, home: executionEnv.homeDir }; const skillGrantLane = skillGrants.lane(listFacts); const allowEntryLanes = { request: allowedToolTables.request.lane(listFacts), deployment: allowedToolTables.deployment.lane(listFacts) }; const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({ provider: deps.permissionRuleStore, localOwnerDeclared: deps.localOwnerRules === true, principal: spec.principal, sessionId, root: taskRootFinal, liveCwd: () => handsCwdRef?.current, home: executionEnv.homeDir, questionToolMounted, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }), onDisclosure: (message) => emitTrace(deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: hostTaskId, message, ts: Date.now() })), onStoreUnreadable: announceRuleStoreSkipped, signal: abortController.signal, stripsAllow: (rule, req) => permissionMode.current === "auto" && autoStrippedRule(rule, req), }); const ruleOffersOf = createRuleOffersOf({ laneArmed: permissionRuleLane !== undefined, principal: spec.principal, localOwnerDeclared: deps.localOwnerRules === true, egressTools, irreversibilityTier, shellGatedTools, toolMarkSources, taskRoot: taskRootFinal, cwdRef: handsCwdRef, deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined, }); const recheckApprovedEdit = async (pol, onAskOf, creq, edit, approved, csignal, ancestorAutoMode) => { const ancestorDecider = ancestorAutoMode?.decider; const ancestorTracker = ancestorAutoMode?.denialTracking; let editArgs = edit; let settledBy = approved; const vetoed = (d) => { const out = { ...d }; carryEngineAttestations(d, out); carryPrecedingSettlement(settledBy, out); return out; }; for (let round = 0;; round++) { if (round >= 3) { return vetoed({ action: "deny", 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`, }); } let re; try { re = refuseOutOfContractDecision(await pol.check({ ...creq, args: editArgs }, csignal ?? abortController.signal)); } catch (err) { return vetoed({ action: "deny", message: `inherited parent policy re-check of the approved edit errored for "${creq.toolName}": ${thrownText(err)}`, }); } if (re.action === "deny") return vetoed(re); if (re.action === "allow") { const stated = re.updatedInput === undefined ? "mode" : re.decisionReason; const out = { action: "allow", updatedInput: re.updatedInput !== undefined ? re.updatedInput : editArgs, ...(stated !== undefined ? { decisionReason: stated } : {}) }; carryEngineAttestations(re, out); carryPrecedingSettlement(settledBy, out); return out; } if (re.updatedInput !== undefined) editArgs = re.updatedInput; const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: ancestorAutoMode, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit", allowRewrite: editArgs, facts: inheritedStationFacts(creq), unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(ancestorDecider?.timeoutMs !== undefined ? { budgetMs: ancestorDecider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(onAskOf), onNotice: deps.onNotice, sessionId, runId }) }); if (judged.kind === "allow") { const cleared = { ...judged.result }; carryEngineAttestations(judged.result, cleared); carryPrecedingSettlement(settledBy, cleared); return cleared; } if (judged.kind === "deny") return vetoed(judged.result); const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged; re = editAsk; const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged); if (editArbitrated !== undefined) { const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs, decisionReason: "mode" } : editArbitrated; carryPrecedingSettlement(settledBy, out); return out; } const rr = await resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: editArgs, ...ruleOffersOf(creq, editArgs, ancestorFacts(re)), ...cardDisclosureOf(re, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(re)), ...inheritedAskCarry(judged, isLiveApproverSeat(onAskOf), ancestorTracker), ruleEvidence: inheritedAskEvidence, }, 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 })); void settleReadRootGrant(rr.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice }); settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId }); if (rr.action !== "allow") return rr; settledBy = rr; if (rr.updatedInput === undefined) { const out = { action: "allow", updatedInput: editArgs, decisionReason: "mode" }; carryPrecedingSettlement(rr, out); return out; } editArgs = rr.updatedInput; } }; const parentConstraintWrappers = (inheritedParentConstraints ?? []).map((pc) => pc.preToolUse !== undefined && pc.preToolUse === hooks?.preToolUse && !preToolUseObservational && pc.durableMandate !== true && pc.contentMandate !== true && askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) && pc.hookEnv === hookEnvSource && pc.autoMode?.decider === autoModeDecider() ? { check: () => ({ action: "allow" }) } : policy !== undefined && pc.policy === policy ? { check: async (creq, csignal) => { const first = sharedFirstDecision.get(creq.toolCallId); if (first === undefined) { return { action: "deny", message: `inherited parent policy could not be arbitrated for "${creq.toolName}" ` + `(shared-instance first decision unavailable); denied fail-closed`, }; } if (first.action === "deny") return first; if (first.action === "allow") return { action: "allow" }; if (isDelegatedQuestion(creq)) { if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId)) return first; return { action: "deny", message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` + `denied fail-closed (tighten-only)`, }; } if (pc.durableMandate === true) { if (mandatedCallFloats(creq.toolCallId)) return first; return mandateUnreconstructibleDeny(creq); } const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call", facts: inheritedStationFacts(creq), unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(pc.autoMode?.decider.timeoutMs !== undefined ? { budgetMs: pc.autoMode.decider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) }); if (judged.kind === "allow") return judged.result; if (judged.kind === "deny") return judged.result; const { ask: inheritedAsk, fallback, mintedHere } = judged; if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) { recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName); return { action: "allow", decisionReason: "sandbox" }; } const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId, judged); if (arbitrated !== undefined) return arbitrated; const presentedArgs = creq.args; const askT0 = now(); const resolved = await resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: presentedArgs, ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)), ...cardDisclosureOf(inheritedAsk, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)), ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking), ruleEvidence: inheritedAskEvidence, }, 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 })); void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice }); const askWaitMs = Math.max(0, now() - askT0); if (resolved.action === "deny" && resolved.approverUnavailable === true) { if (markInheritedUnavailable(creq.toolCallId)) return inheritedAsk; return markerChannelFullDeny(creq); } settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId }); if (resolved.action !== "allow") return resolved; if (resolved.updatedInput !== undefined) { return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode); } recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved, askClassMembersOf(judged.classification).classes); return { action: "allow", decisionReason: "mode" }; }, } : wrapToolPolicy(pc.policy, async (creq, csignal) => { let decision; try { decision = refuseOutOfContractDecision(await pc.policy.check(creq, csignal ?? abortController.signal)); } catch (err) { return { action: "deny", message: `inherited parent policy errored for "${creq.toolName}": ${thrownText(err)}`, }; } if (decision.action !== "ask") return decision; if (isDelegatedQuestion(creq)) { if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId)) return decision; return { action: "deny", message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` + `denied fail-closed (tighten-only)`, }; } if (pc.durableMandate === true) { if (mandatedCallFloats(creq.toolCallId)) return decision; return mandateUnreconstructibleDeny(creq); } const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args; const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call", allowRewrite: decision.updatedInput, facts: inheritedStationFacts(creq), unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(pc.autoMode?.decider.timeoutMs !== undefined ? { budgetMs: pc.autoMode.decider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) }); if (judged.kind === "allow") return judged.result; if (judged.kind === "deny") return judged.result; const { ask: inheritedAsk, fallback, mintedHere } = judged; if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) { recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName); return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput, decisionReason: "sandbox" } : { action: "allow", decisionReason: "sandbox" }; } const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId, judged); if (arbitrated !== undefined) return arbitrated; const askT0 = now(); const resolved = await resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: presentedArgs, ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)), ...cardDisclosureOf(inheritedAsk, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)), ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking), ruleEvidence: inheritedAskEvidence, }, 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 })); void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice }); const askWaitMs = Math.max(0, now() - askT0); if (resolved.action === "deny" && resolved.approverUnavailable === true) { if (markInheritedUnavailable(creq.toolCallId)) return inheritedAsk; return markerChannelFullDeny(creq); } settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId }); if (resolved.action !== "allow") return resolved; if (resolved.updatedInput !== undefined) { return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode); } if (pc.preToolUse === undefined) { recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved, askClassMembersOf(judged.classification).classes); } if (decision.updatedInput === undefined) return resolved; const approvedWithRewrite = { ...resolved, updatedInput: decision.updatedInput }; carryEngineAttestations(resolved, approvedWithRewrite); return approvedWithRewrite; })); const rewriteCapableLayers = policy !== undefined || parentConstraintWrappers.length > 0; const rewriteEmitterIndex = new Map(); const rewriteCapableChain = [ ...(callerPolicyLayer !== undefined ? [callerPolicyLayer] : []), ...parentConstraintWrappers, ]; const callerSlotOffset = callerPolicyLayer !== undefined ? 1 : 0; const sameInstanceAdapterIndices = new Set(); (inheritedParentConstraints ?? []).forEach((pc, j) => { if (policy !== undefined && pc.policy === policy) sameInstanceAdapterIndices.add(j + callerSlotOffset); }); const instrumentedChain = rewriteCapableChain.map((layer, idx) => wrapToolPolicy(layer, async (creq, csignal) => { const d = refuseOutOfContractDecision(await layer.check(creq, csignal)); if (d.updatedInput !== undefined) rewriteEmitterIndex.set(creq.toolCallId, idx); if (d.action === "ask") { recordFoldAskClass(creq.toolCallId, idx < callerSlotOffset ? policyAskClassOf(policy) : "mandate", idx < callerSlotOffset ? "caller_policy" : "ancestor_constraint"); } return d; })); const modeGateLayer = createModeGatePolicy(permissionMode, { env: executionEnv, rootPath: taskRootFinal, acceptDirs: [taskRootFinal, ...additionalRootsCanonical], roster: toolRosterDeltas, readOnlyDeclared: (name) => mountedToolDeclaresReadOnly(tools, name), ...(scratchpadDir !== undefined ? { exemptDirs: [scratchpadDir] } : {}) }); const policyLayers = [ ...narrowingLayers, ...instrumentedChain, ...(rewriteCapableLayers ? narrowingLayers : []), ]; const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy()]; const guardedPolicyLayers = rewriteCapableLayers ? [...integrityGuardLayers, ...policyLayers, modeGateLayer, ...integrityGuardLayers] : [...integrityGuardLayers, ...policyLayers, modeGateLayer]; const foldedPolicy = guardedPolicyLayers.length === 1 ? guardedPolicyLayers[0] : combinePolicies(...guardedPolicyLayers); const effectivePolicy = foldedPolicy !== undefined && rewriteCapableLayers ? wrapToolPolicy(foldedPolicy, async (req, signal) => { let decision; try { decision = await foldedPolicy.check(req, signal); } catch (err) { rewriteEmitterIndex.delete(req.toolCallId); throw err; } const emitterIdx = rewriteEmitterIndex.get(req.toolCallId); rewriteEmitterIndex.delete(req.toolCallId); if (decision.action === "deny" || decision.updatedInput === undefined) return decision; const recheckPrefix = rewriteCapableChain.slice(0, emitterIdx ?? rewriteCapableChain.length); const postEmitterSameInstanceAdapters = []; if (emitterIdx !== undefined && emitterIdx > 0) { for (let i = emitterIdx + 1; i < rewriteCapableChain.length; i++) { if (sameInstanceAdapterIndices.has(i)) postEmitterSameInstanceAdapters.push(rewriteCapableChain[i]); } } const recheckLayers = [...recheckPrefix, ...postEmitterSameInstanceAdapters]; const finalReq = { ...req, args: decision.updatedInput }; let asked; const revalidated = {}; let restated; try { for (const layer of recheckLayers) { const d = refuseOutOfContractDecision(await layer.check(finalReq, signal)); if (d.action === "deny") { const carried = { ...d, updatedInput: decision.updatedInput }; carryEngineAttestations(d, carried); carryPrecedingSettlement(decision, carried); return carried; } if (d.updatedInput !== undefined) { return { action: "deny", 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)", updatedInput: decision.updatedInput, }; } if (d.action === "ask" && asked === undefined) asked = d; if (d.action === "allow" && restated === undefined && d.decisionReason !== undefined) restated = d.decisionReason; carryEngineAttestations(d, revalidated); } } finally { rewriteEmitterIndex.delete(req.toolCallId); } if (asked !== undefined && decision.action === "allow") { const surfaced = { ...asked, updatedInput: decision.updatedInput }; carryEngineAttestations(asked, surfaced); return surfaced; } if (decision.action === "allow") { const owned = { ...decision, ...(decision.decisionReason === undefined && restated !== undefined ? { decisionReason: restated } : {}) }; carryEngineAttestations(decision, owned); carryEngineAttestations(revalidated, owned); return owned; } return decision; }) : foldedPolicy; return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations }; } const advisedPolicyNames = new WeakMap(); const ADVISED_KEYS_CAP = 64;