UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

680 lines (679 loc) • 47.5 kB
import { argsAsTheToolReads, withArgsReading } from "../effective-path-target.js"; 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 { chainJudges, inheritedToolChecks, screeningInstallationsOf, standsInFor } from "../mods/stations.js"; import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js"; import { isIsolated } from "../remote-env.js"; import { deliverEngineNotice } from "../engine-notice.js"; import { earlierGrammarNotice } from "../session-policy-store.js"; import { attest, attestationOf, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, mergeAttestation, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, withAttestation, wrapToolPolicy } from "../tool-policy.js"; import { judgeRuleToolName, mountedToolNames } from "../tool-name-aliases.js"; import { approvalCardMemberOf } from "../tool-roster.js"; import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js"; import { emitTrace } from "../trace.js"; import { skillGrantBindingMembers } from "../skill-grant.js"; import { SKILL_TOOL_NAME } from "./synthetic-tools.js"; import { delegationPoolReading } from "./sub-agent-deny-lists.js"; import { valveLeaves } from "../../agents/tool-filter.js"; import { autoStrippedRule, cardDisclosureOf, nearMissSpellingOf, rootFacingToolNames } from "../allow-entry.js"; import { arbitrateByMode, askClassMembersOf, askFaceOf, 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, resolveInheritedAsk, settleDenialLimitFallback } from "./denial-limit-arms.js"; import { settleReadRootGrant } from "./read-root-grant.js"; import { frozenApproverRoute } from "../wiring-manifest.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 { policyThrowLine, reportPolicyThrow } from "./policy-throw.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, pathRuleAnchors, headLeads, tools, harnessTools, selector, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, personRows, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, modBus, ownModChain, autoModeDecider, readFace, 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, inherited = false) => { const nameGroups = toolPolicyNameSets(auditee); if (nameGroups.length > 0) { const known = mountedToolNames(tools); for (const t of harnessTools) known.add(t.name); const pool = delegationPoolReading(spec.tools ?? [], valveLeaves(spec, selector)); const callerNames = mountedToolNames(spec.tools ?? []); const rootFacing = rootFacingToolNames([...known, ...callerNames, ...pool.names]); 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); const twin = verdict.kind === "current" && !inherited ? nearMissSpellingOf(n, rootFacing) : undefined; const stale = verdict.kind !== "current" ? verdict : twin !== undefined ? { kind: "case_twin", twin, carrier: known.has(twin) || callerNames.has(twin) ? "run" : pool.names.has(twin) ? "pool" : "catalog" } : undefined; if (stale !== undefined) { const entry = `tool policy ${kind}-list entry "${n}"`; let message; switch (stale.kind) { case "retired": message = `${entry} is a RETIRED tool name (${stale.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.`; break; case "unprefixed_namespace": message = `${entry} 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.`; break; case "unmintable_segment": message = `${entry} can match no tool, ever (${stale.guidance}), so it would silently guard nothing.${stale.currentName !== undefined ? ` Write "${stale.currentName}".` : ""}`; break; case "case_twin": message = `${entry} differs from ${stale.carrier === "run" ? "the mounted tool" : stale.carrier === "pool" ? "the tool a delegation pool carries" : "the engine's catalog tool"} "${stale.twin}" only in case, and a policy compares tool names exactly, so it matches nothing in this run's roster and would silently guard nothing. Write "${stale.twin}".`; break; default: { const exhaustive = stale; return exhaustive; } } const err = new Error(message); 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 { } }; const announceEarlierGrammar = (rulesSessionId) => (entries, field, grammar) => deliverEngineNotice(deps.onNotice, earlierGrammarNotice({ rulesSessionId, sessionId, entries, field, grammar })); 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, anchors: pathRuleAnchors.leg, onEarlierGrammar: announceEarlierGrammar(sessionRuleKey.sessionId) }); 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, anchors: pathRuleAnchors.leg, onEarlierGrammar: announceEarlierGrammar(snap.sessionId) })); } 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); auditPolicyNames(sessionRulePolicy); for (const layer of ancestorRulePolicies) auditPolicyNames(layer, true); const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers); const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy; const ownChainJudges = chainJudges(modBus); 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) => attest({ 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)`, }, creq, { unresolvableCause: "mandate_unreconstructible" }); const markerChannelFullDeny = (creq) => attest({ action: "deny", message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed` }, creq, { unresolvableCause: "approver_unavailable" }); 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 grantBindingOf = (call, args) => skillGrantBindingMembers(call, args, skillSpecs, SKILL_TOOL_NAME); const inheritedStationFacts = (call) => { const seat = seatMarksOf(call); const face = readFace(); 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 } : {}), ...(face !== undefined ? { readFace: face } : {}), askFace: askFaceOf(wireOf("inherited.askFace", call)), }; }; const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore); const listFacts = { root: taskRootFinal, sessionId, liveCwd: () => handsCwdRef?.current, anchors: pathRuleAnchors.leg }; const skillGrantLane = skillGrants.lane(listFacts); const allowEntryLanes = { request: allowedToolTables.request.lane(listFacts), deployment: allowedToolTables.deployment.lane(listFacts) }; const ruleLaneFacts = { sessionId, root: taskRootFinal, rootCanonical: pathRuleAnchors.leg.root, chainTop: pathRuleAnchors.chainTop, leads: headLeads, 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() })), stripsAllow: (rule, req) => permissionMode.current === "auto" && autoStrippedRule(rule, req), }; const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({ provider: deps.permissionRuleStore, localOwnerDeclared: deps.localOwnerRules === true, principal: spec.principal, ...ruleLaneFacts, rows: personRows }); 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 = argsAsTheToolReads(creq, edit); let settledBy = approved; const onApproval = (d) => withAttestation(d, mergeAttestation(creq, attestationOf(settledBy, creq), attestationOf(d, creq), "answer")); const vetoed = onApproval; 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(await withArgsReading(creq, editArgs, csignal ?? abortController.signal), csignal ?? abortController.signal)); } catch (err) { reportPolicyThrow(notifyOwnHookCrash, "an inherited parent policy's re-check of the approved edit", creq, err, csignal ?? abortController.signal); return vetoed({ action: "deny", message: `inherited parent policy re-check of the approved edit errored for "${creq.toolName}": ${policyThrowLine(err)}`, }); } if (re.action === "deny") return vetoed(re); if (re.action === "allow") { const stated = re.updatedInput === undefined ? "mode" : re.decisionReason; return onApproval(withAttestation({ action: "allow", updatedInput: re.updatedInput !== undefined ? re.updatedInput : editArgs, ...(stated !== undefined ? { decisionReason: stated } : {}) }, re.attestation)); } if (re.updatedInput !== undefined) editArgs = argsAsTheToolReads(creq, 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 } : {}), route: frozenApproverRoute(onAskOf), onNotice: deps.onNotice, sessionId, runId }) }); if (judged.kind === "allow") { return onApproval(judged.result); } if (judged.kind === "deny") return vetoed(judged.result); const { ask: editAsk } = judged; re = editAsk; const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged); if (editArbitrated !== undefined) { return onApproval(editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs, decisionReason: "mode" } : editArbitrated); } const { resolved: rr, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: editArgs, ...ruleOffersOf(creq, editArgs, ancestorFacts(j.ask)), ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...approvalCardMemberOf(creq), ...grantBindingOf(creq, editArgs), ...(j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(j.ask)), ...inheritedAskCarry(j, 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 })), ancestorTracker, creq, inheritedStationFacts(creq)); void settleReadRootGrant(rr.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice }); settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.mintedHere, 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) return onApproval({ action: "allow", updatedInput: editArgs, decisionReason: "mode" }); editArgs = argsAsTheToolReads(creq, rr.updatedInput); } }; const settleInstallationAsk = async (pc, decision, creq, csignal) => { const settledAs = (result) => ({ kind: "settled", result }); if (isDelegatedQuestion(creq)) { if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId)) return settledAs(decision); return settledAs({ 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 settledAs(decision); return settledAs(mandateUnreconstructibleDeny(creq)); } const presentedArgs = decision.updatedInput !== undefined ? argsAsTheToolReads(creq, 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 } : {}), route: frozenApproverRoute(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) }); if (judged.kind === "allow") return settledAs(judged.result); if (judged.kind === "deny") return settledAs(judged.result); const { ask: inheritedAsk, fallback } = judged; if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) { recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName); return settledAs(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 settledAs(arbitrated); const askT0 = now(); const { resolved, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: presentedArgs, ...ruleOffersOf(creq, presentedArgs, ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)), ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...approvalCardMemberOf(creq), ...grantBindingOf(creq, presentedArgs), ...(j.ask.action === "ask" && j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)), ...inheritedAskCarry(j, 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 })), pc.autoMode?.denialTracking, creq, inheritedStationFacts(creq)); 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 settledAs(settled.ask); return settledAs(markerChannelFullDeny(creq)); } settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId }); if (resolved.action !== "allow") return settledAs(resolved); return { kind: "approved", resolved, askWaitMs, classes: askClassMembersOf(judged.classification).classes }; }; const legStandsIn = (pc) => standsInFor(pc, { chain: ownModChain, onAsk: frozenOnAsk, hookEnv: hookEnvSource, decider: autoModeDecider() }, ownChainJudges); const parentConstraintWrappers = (inheritedParentConstraints ?? []).map((pc) => legStandsIn(pc) ? { 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 } : {}), route: frozenApproverRoute(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 } = 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, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({ toolName: creq.toolName, toolCallId: creq.toolCallId, args: presentedArgs, ...ruleOffersOf(creq, presentedArgs, ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)), ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)), ...askSourceIdentity(), ...riskAxesOf(creq), ...approvalCardMemberOf(creq), ...grantBindingOf(creq, presentedArgs), ...(j.ask.action === "ask" && j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}), ...askMandateMember(ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)), ...inheritedAskCarry(j, 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 })), pc.autoMode?.denialTracking, creq, inheritedStationFacts(creq)); 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 settled.ask; return markerChannelFullDeny(creq); } settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.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(settled.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) { reportPolicyThrow(notifyOwnHookCrash, "an inherited parent policy", creq, err, csignal ?? abortController.signal); return { action: "deny", message: `inherited parent policy errored for "${creq.toolName}": ${policyThrowLine(err)}`, }; } if (decision.action !== "ask") return decision; const settled = await settleInstallationAsk(pc, decision, creq, csignal); if (settled.kind === "settled") return settled.result; const { resolved } = settled; if (resolved.updatedInput !== undefined) { return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode); } if (screeningInstallationsOf([pc]).length === 0) { recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, settled.askWaitMs, resolved, settled.classes); } if (decision.updatedInput === undefined) return resolved; return { ...resolved, updatedInput: decision.updatedInput }; })); const installationToolChecks = inheritedToolChecks(inheritedParentConstraints ?? [], legStandsIn, (pc) => async (ask, creq, csignal) => { const settled = await settleInstallationAsk(pc, ask, creq, csignal); return settled.kind === "settled" ? settled.result : settled.resolved; }); 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({ removalSeat: { env: executionEnv, additionalRoots: () => additionalRootsCanonical } })]; 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 = await withArgsReading(req, decision.updatedInput, signal); let asked; let revalidated; let restated; try { for (const layer of recheckLayers) { const d = refuseOutOfContractDecision(await layer.check(finalReq, signal)); if (d.action === "deny") { return withAttestation({ ...d, updatedInput: decision.updatedInput }, mergeAttestation(req, attestationOf(decision, req), attestationOf(d, req), "answer")); } 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; revalidated = mergeAttestation(req, revalidated, d.attestation); } } finally { rewriteEmitterIndex.delete(req.toolCallId); } if (asked !== undefined && decision.action === "allow") { return { ...asked, updatedInput: decision.updatedInput }; } if (decision.action === "allow") { const owned = { ...decision, ...(decision.decisionReason === undefined && restated !== undefined ? { decisionReason: restated } : {}) }; return withAttestation(owned, mergeAttestation(req, attestationOf(decision, req), revalidated)); } return decision; }) : foldedPolicy; return { basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, ruleLaneFacts, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations, inheritedToolChecks: installationToolChecks }; } const advisedPolicyNames = new WeakMap(); const ADVISED_KEYS_CAP = 64;