@sema-agent/core 5.21.0 → 5.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/agents/send-message-tool.js +6 -3
  3. package/dist/agents/subagent.d.ts +6 -0
  4. package/dist/agents/subagent.js +45 -4
  5. package/dist/brain/errors.d.ts +20 -0
  6. package/dist/brain/errors.js +40 -0
  7. package/dist/brain/retry.d.ts +16 -2
  8. package/dist/brain/retry.js +3 -2
  9. package/dist/brain/status-sink.d.ts +9 -2
  10. package/dist/brain/stream-engine.d.ts +22 -0
  11. package/dist/brain/stream-engine.js +41 -10
  12. package/dist/core/ask-class.d.ts +48 -0
  13. package/dist/core/ask-class.js +33 -0
  14. package/dist/core/checkpoint-store.d.ts +103 -10
  15. package/dist/core/checkpoint-store.js +3 -1
  16. package/dist/core/governance-codes.d.ts +38 -0
  17. package/dist/core/governance-codes.js +11 -0
  18. package/dist/core/hooks.d.ts +39 -0
  19. package/dist/core/hooks.js +26 -2
  20. package/dist/core/locked-config.d.ts +7 -1
  21. package/dist/core/locked-config.js +2 -1
  22. package/dist/core/memory-engine/delegation-provenance.d.ts +62 -0
  23. package/dist/core/memory-engine/delegation-provenance.js +26 -0
  24. package/dist/core/memory-engine/engine.d.ts +67 -1
  25. package/dist/core/memory-engine/engine.js +270 -12
  26. package/dist/core/memory-engine/header-hints.d.ts +30 -0
  27. package/dist/core/memory-engine/header-hints.js +41 -0
  28. package/dist/core/memory-engine/index.d.ts +3 -2
  29. package/dist/core/memory-engine/index.js +3 -2
  30. package/dist/core/memory-engine/layout.d.ts +166 -0
  31. package/dist/core/memory-engine/layout.js +399 -0
  32. package/dist/core/memory-engine/tools.d.ts +30 -0
  33. package/dist/core/memory-engine/tools.js +108 -17
  34. package/dist/core/permission-rule-consent.d.ts +25 -9
  35. package/dist/core/permission-rule-consent.js +91 -20
  36. package/dist/core/permission-rule-model.d.ts +9 -1
  37. package/dist/core/permission-rule-model.js +2 -2
  38. package/dist/core/permission-rule-org.d.ts +161 -0
  39. package/dist/core/permission-rule-org.js +211 -0
  40. package/dist/core/permission-rule-store.d.ts +249 -6
  41. package/dist/core/permission-rule-store.js +313 -3
  42. package/dist/core/permission-rule-sync.d.ts +131 -0
  43. package/dist/core/permission-rule-sync.js +314 -0
  44. package/dist/core/runner/prepare-memory.js +35 -8
  45. package/dist/core/runner/prepare-task.d.ts +54 -1
  46. package/dist/core/runner/prepare-task.js +246 -27
  47. package/dist/core/runner/runtask.js +147 -6
  48. package/dist/core/shared-memory/contract.js +19 -4
  49. package/dist/core/shared-memory/normalize.d.ts +3 -1
  50. package/dist/core/shared-memory/tools.js +73 -17
  51. package/dist/core/shared-memory/types.d.ts +27 -1
  52. package/dist/core/store-contracts/permission-rule-sync-contract.d.ts +33 -0
  53. package/dist/core/store-contracts/permission-rule-sync-contract.js +186 -0
  54. package/dist/core/task-notification.d.ts +5 -2
  55. package/dist/core/task-registry-agent.d.ts +1 -1
  56. package/dist/core/task-registry-agent.js +6 -2
  57. package/dist/core/task-registry-shared.d.ts +9 -2
  58. package/dist/core/task-registry.d.ts +9 -3
  59. package/dist/core/task-registry.js +2 -0
  60. package/dist/core/tool-policy.d.ts +120 -2
  61. package/dist/core/tool-policy.js +116 -6
  62. package/dist/core/trace.d.ts +32 -1
  63. package/dist/core/types.d.ts +56 -3
  64. package/dist/index.d.ts +12 -7
  65. package/dist/index.js +10 -5
  66. package/dist/stores/file/checkpoint-store.d.ts +4 -0
  67. package/dist/stores/file/checkpoint-store.js +1 -0
  68. package/dist/stores/file/permission-rule-adopt.d.ts +62 -0
  69. package/dist/stores/file/permission-rule-adopt.js +95 -0
  70. package/dist/stores/file/permission-rule-store.d.ts +80 -2
  71. package/dist/stores/file/permission-rule-store.js +189 -46
  72. package/dist/tools/fs/fs-search-tools.js +0 -1
  73. package/package.json +1 -1
@@ -20,12 +20,13 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
20
20
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
21
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
22
22
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
23
- import { askApproverIdentity, combinePolicies, isApprovalSettledBy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
23
+ import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
24
  const PERSISTED_RULE_TOOL = "Bash";
25
25
  import { findAdmittingRule, suggestRulesForCommand } from "../permission-rule-model.js";
26
26
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
27
27
  import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
28
28
  import { inlineUntrusted } from "../untrusted-text.js";
29
+ import { policyAskClassOf } from "../ask-class.js";
29
30
  import { emitTrace } from "../trace.js";
30
31
  import { createSessionRulePolicy } from "./session-rule-policy.js";
31
32
  import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, runToolGate } from "../hooks.js";
@@ -46,6 +47,7 @@ import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
46
47
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
47
48
  import { MEMORY_RECALL_DISCIPLINE } from "../memory-engine/engine.js";
48
49
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
50
+ import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
49
51
  import { composeMemoryBlock } from "../memory.js";
50
52
  import { preflightLockedConfig } from "../locked-config.js";
51
53
  import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
@@ -82,7 +84,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
82
84
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
83
85
  import { resolveKey } from "../../tools/fs/safety.js";
84
86
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
85
- import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
87
+ import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
86
88
  import { boundInputHashOf } from "../canonical-json.js";
87
89
  import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
88
90
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
@@ -1057,6 +1059,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1057
1059
  const memoryAdmittedOrgScopesRef = { current: [] };
1058
1060
  const ownOrgVerdictRef = { current: undefined };
1059
1061
  const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
1062
+ const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
1063
+ seedInheritedGate?.constraintChain !== undefined ||
1064
+ internals?.delegationProvenance !== undefined;
1060
1065
  const frozenOnAsk = spec.onAsk ?? deps.onAsk;
1061
1066
  const hookEnvSource = (ownedEnv ?? deps.executionEnv) != null ? executionEnv : undefined;
1062
1067
  const notifyOwnHookCrash = (err) => {
@@ -1067,6 +1072,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1067
1072
  }
1068
1073
  };
1069
1074
  const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
1075
+ const provenanceForChildrenRef = {};
1076
+ const delegationProvenanceForChildren = () => provenanceForChildrenRef.current;
1070
1077
  const inheritedGateForChildren = () => {
1071
1078
  const ancestorRules = [
1072
1079
  ...(inheritedAncestorRules ?? []),
@@ -1075,12 +1082,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1075
1082
  const ownCallerPolicy = lockedPreflight.toolPolicy;
1076
1083
  const durableMandate = runtimeCaps?.forceDurableGate === true ||
1077
1084
  (spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
1085
+ const contentMandate = runtimeCaps?.forceDurableGate === true ||
1086
+ (spec.durableApproval !== undefined && !isLiveQuestionFace(frozenOnQuestion));
1078
1087
  const ownPreToolUse = preToolUseObservational ? undefined : hooks?.preToolUse;
1079
1088
  const hookConstraint = ownPreToolUse !== undefined &&
1080
1089
  !(inheritedParentConstraints ?? []).some((pc) => pc.preToolUse === ownPreToolUse &&
1081
1090
  askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
1082
1091
  (pc.durableMandate === true) === durableMandate &&
1083
- pc.hookEnv === hookEnvSource)
1092
+ (pc.contentMandate === true) === contentMandate &&
1093
+ pc.hookEnv === hookEnvSource &&
1094
+ pc.autoMode?.decider === autoModeDecider)
1084
1095
  ? [
1085
1096
  {
1086
1097
  policy: createPreToolUseConstraintPolicy(ownPreToolUse, hookEnvFace, notifyOwnHookCrash),
@@ -1088,6 +1099,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1088
1099
  ...(hookEnvSource !== undefined ? { hookEnv: hookEnvSource } : {}),
1089
1100
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1090
1101
  ...(durableMandate ? { durableMandate: true } : {}),
1102
+ ...(contentMandate ? { contentMandate: true } : {}),
1103
+ ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1091
1104
  },
1092
1105
  ]
1093
1106
  : [];
@@ -1100,6 +1113,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1100
1113
  policy: ownCallerPolicy,
1101
1114
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1102
1115
  ...(durableMandate ? { durableMandate: true } : {}),
1116
+ ...(contentMandate ? { contentMandate: true } : {}),
1117
+ ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1103
1118
  },
1104
1119
  ]
1105
1120
  : []),
@@ -1140,6 +1155,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1140
1155
  ...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
1141
1156
  activeSkillScope: () => skillScope.active(),
1142
1157
  inheritedGateForChildren,
1158
+ delegationProvenanceForChildren,
1143
1159
  ...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
1144
1160
  ...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
1145
1161
  ...(spec.checkpointStore === null ? { checkpointStoreDisabledForChildren: true } : {}),
@@ -1939,6 +1955,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1939
1955
  if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
1940
1956
  internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
1941
1957
  }
1958
+ if (internals?.delegationProvenance !== undefined && resume !== undefined) {
1959
+ const live = internals.delegationProvenance.ref.current;
1960
+ const seed = resume.seed.delegationProvenance;
1961
+ if (seed === undefined || seed.version !== 1 || seed.coverageStartTurn !== 0) {
1962
+ live.incomplete = true;
1963
+ }
1964
+ else {
1965
+ if (seed.sawExternal)
1966
+ live.sawExternal = true;
1967
+ if (seed.incomplete)
1968
+ live.incomplete = true;
1969
+ }
1970
+ }
1942
1971
  let memoryBlock = memoryBlockFromEngine;
1943
1972
  if (memorySeedFiles?.length && seedContextFiles) {
1944
1973
  try {
@@ -2312,15 +2341,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2312
2341
  else {
2313
2342
  deferred = classifyDeferredOverFace(tools, []);
2314
2343
  }
2315
- if (memoryEngineSession !== undefined) {
2316
- const { pollution, contentSafety } = memoryEngineSession;
2344
+ const delegationProvenanceChannel = internals?.delegationProvenance;
2345
+ if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
2346
+ const pollution = memoryEngineSession?.pollution;
2347
+ const effectiveSafety = narrowContentSafety(delegationProvenanceChannel?.contentSafety, memoryEngineSession?.contentSafety);
2348
+ const trustedTools = effectiveSafety.trustedTools;
2349
+ const execIsExternalContent = effectiveSafety.execIsExternalContent;
2350
+ provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
2351
+ const recordExternal = () => {
2352
+ if (delegationProvenanceChannel !== undefined)
2353
+ delegationProvenanceChannel.ref.current.sawExternal = true;
2354
+ };
2355
+ const recordIncomplete = () => {
2356
+ if (delegationProvenanceChannel !== undefined)
2357
+ delegationProvenanceChannel.ref.current.incomplete = true;
2358
+ };
2317
2359
  const callerToolNames = new Set((spec.tools ?? []).map((t) => t.name));
2318
2360
  const contentOriginWrapped = new WeakSet();
2319
2361
  const classOf = (t) => classifyToolContentOrigin({
2320
2362
  declared: t.contentOrigin,
2321
2363
  isProtocolTool: protocolOf(t.name) !== undefined,
2322
2364
  isCallerTool: callerToolNames.has(t.name),
2323
- trusted: contentSafety.trustedTools.has(t.name),
2365
+ trusted: trustedTools.has(t.name),
2324
2366
  });
2325
2367
  const delegationByName = new Map();
2326
2368
  for (const t of spec.tools ?? []) {
@@ -2333,8 +2375,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2333
2375
  declared: t.contentOrigin,
2334
2376
  isProtocolTool: protocolOf(t.name) !== undefined,
2335
2377
  isCallerTool: true,
2336
- trusted: contentSafety.trustedTools.has(t.name),
2337
- }), contentSafety.execIsExternalContent);
2378
+ trusted: trustedTools.has(t.name),
2379
+ }), execIsExternalContent);
2338
2380
  contentOriginWrapRef.current = () => {
2339
2381
  for (let i = 0; i < tools.length; i++) {
2340
2382
  const t = tools[i];
@@ -2343,7 +2385,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2343
2385
  const origin = classOf(t);
2344
2386
  const delegation = delegationByName.get(t.name);
2345
2387
  const isDelegation = delegation !== undefined;
2346
- const pollutes = contentOriginPollutes(origin, contentSafety.execIsExternalContent);
2388
+ const pollutes = contentOriginPollutes(origin, execIsExternalContent);
2347
2389
  if (!pollutes && !isDelegation) {
2348
2390
  contentOriginWrapped.add(t);
2349
2391
  continue;
@@ -2351,7 +2393,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2351
2393
  const inner = t.execute.bind(t);
2352
2394
  const mark = (reason) => {
2353
2395
  try {
2354
- pollution.markPolluted(reason);
2396
+ pollution?.markPolluted(reason);
2355
2397
  }
2356
2398
  catch {
2357
2399
  }
@@ -2359,8 +2401,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2359
2401
  const wrapped = {
2360
2402
  ...t,
2361
2403
  execute: async (toolCallId, params, signal, onUpdate) => {
2362
- if (pollutes)
2404
+ if (pollutes) {
2363
2405
  mark(`tool "${t.name}" (${origin} content class) was invoked in this session`);
2406
+ recordExternal();
2407
+ }
2364
2408
  if (!isDelegation)
2365
2409
  return inner(toolCallId, params, signal, onUpdate);
2366
2410
  const requested = params?.subagent_type;
@@ -2371,9 +2415,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2371
2415
  isPolluting: poolToolPollutes,
2372
2416
  });
2373
2417
  const result = await inner(toolCallId, params, signal, onUpdate);
2374
- if (external) {
2375
- mark(`tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
2376
- (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named classified fail-closed)"));
2418
+ const att = readCardAttestation(result?.details);
2419
+ if (att === "external") {
2420
+ mark(`tool "${t.name}" returned the output of a delegated agent that invoked an external-content-class tool at runtime` +
2421
+ (typeof requested === "string" ? ` (subagent_type "${requested}")` : ""));
2422
+ recordExternal();
2423
+ }
2424
+ else if (att !== "clean") {
2425
+ if (external) {
2426
+ mark(`tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
2427
+ (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)"));
2428
+ }
2429
+ recordIncomplete();
2377
2430
  }
2378
2431
  return result;
2379
2432
  },
@@ -2941,11 +2994,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2941
2994
  toolEffects,
2942
2995
  })
2943
2996
  : undefined;
2944
- const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, skillScopePolicy].filter((p) => p !== undefined);
2997
+ const frozenConstraintLayers = (seedInheritedGate?.constraintChain ?? [])
2998
+ .filter((e) => !("opaque" in e))
2999
+ .map((entry) => ({
3000
+ check: (creq) => checkToolPolicyProjection(entry, creq) ?? { action: "allow" },
3001
+ }));
3002
+ const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, ...frozenConstraintLayers, skillScopePolicy].filter((p) => p !== undefined);
2945
3003
  for (const layer of narrowingLayers)
2946
3004
  auditPolicyNames(layer);
2947
3005
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
2948
- const basePolicyForResumeEdit = lockedPreflight.toolPolicy;
3006
+ const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy;
2949
3007
  const hooks = spec.hooks ?? deps.hooks;
2950
3008
  const preToolUseObservational = hooks?.preToolUse !== undefined && hooks.preToolUseObservational === true;
2951
3009
  const ownGatePreToolUse = !preToolUseObservational
@@ -2997,6 +3055,62 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2997
3055
  return;
2998
3056
  inheritedAskGrants.set(toolCallId, { approver, argsJson, presented, waitMs });
2999
3057
  };
3058
+ const sandboxDeclared = isIsolated(executionEnv);
3059
+ const sandboxAdmissionArmed = (() => {
3060
+ if (!sandboxDeclared)
3061
+ return false;
3062
+ const store = resolveCheckpointStore(spec, deps);
3063
+ const missing = [];
3064
+ if (store === undefined)
3065
+ missing.push("a wired CheckpointStore");
3066
+ else if (store.redecision?.reopen !== true)
3067
+ missing.push("a CheckpointStore that DECLARES redecision.reopen (declaration, never duck-typing)");
3068
+ if (spec.durableApproval === undefined && runtimeCaps?.forceDurableGate !== true)
3069
+ missing.push("a durable park (spec.durableApproval, or the forceDurableGate entitlement)");
3070
+ if (missing.length === 0)
3071
+ return true;
3072
+ try {
3073
+ const e = new Error(`sandbox admission mode NOT armed: the execution env declares an isolated sandbox (capabilities.isolation), but the mode's ` +
3074
+ `park prerequisites are missing — ${missing.join("; ")}. Asks keep the conservative default chain (no auto-admission, no boundary park).`);
3075
+ e.code = "config.sandbox_admission_unavailable";
3076
+ deps.onError?.(e, { phase: "config", sessionId, classification: "sandbox-admission-unavailable" });
3077
+ }
3078
+ catch {
3079
+ }
3080
+ return false;
3081
+ })();
3082
+ const foldAskClasses = new Map();
3083
+ const FOLD_ASK_CLASSES_CAP = 256;
3084
+ const recordFoldAskClass = (toolCallId, cls, layer) => {
3085
+ if (!foldAskClasses.has(toolCallId) && foldAskClasses.size >= FOLD_ASK_CLASSES_CAP)
3086
+ return;
3087
+ const list = foldAskClasses.get(toolCallId) ?? [];
3088
+ list.push({ cls, layer });
3089
+ foldAskClasses.set(toolCallId, list);
3090
+ };
3091
+ const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName);
3092
+ const emitSandboxAdmitted = (info) => {
3093
+ emitTrace(deps.tracer, () => ({
3094
+ kind: "permission.sandbox_admitted",
3095
+ version: 1,
3096
+ taskId: spec.taskId ?? sessionId,
3097
+ toolCallId: info.toolCallId,
3098
+ toolName: info.toolName,
3099
+ askClasses: info.askClasses.map((a) => a.cls),
3100
+ sourceLayers: info.askClasses.map((a) => a.layer),
3101
+ boundary: info.boundary,
3102
+ ts: Date.now(),
3103
+ }));
3104
+ };
3105
+ const ancestorSandboxAdmissions = new Map();
3106
+ const recordAncestorSandboxAdmission = (toolCallId, toolName) => {
3107
+ const list = ancestorSandboxAdmissions.get(toolCallId);
3108
+ if (list !== undefined)
3109
+ list.push({ cls: "sandbox_local", layer: "ancestor_constraint" });
3110
+ else
3111
+ ancestorSandboxAdmissions.set(toolCallId, [{ cls: "sandbox_local", layer: "ancestor_constraint" }]);
3112
+ void toolName;
3113
+ };
3000
3114
  const callerPolicyLayer = policy !== undefined && sameInstanceAncestorCount > 0
3001
3115
  ? {
3002
3116
  check: async (creq, csignal) => {
@@ -3069,7 +3183,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3069
3183
  const suggestions = suggestRulesForCommand(command);
3070
3184
  return suggestions.length > 0 ? { ruleSuggestions: suggestions } : {};
3071
3185
  };
3072
- const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal) => {
3186
+ const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
3073
3187
  let editArgs = edit;
3074
3188
  for (let round = 0;; round++) {
3075
3189
  if (round >= 3) {
@@ -3095,6 +3209,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3095
3209
  }
3096
3210
  if (re.updatedInput !== undefined)
3097
3211
  editArgs = re.updatedInput;
3212
+ if (ancestorDecider !== undefined) {
3213
+ const verdict = await ancestorDecider
3214
+ .decide({ req: { ...creq, args: editArgs }, ...(re.message !== undefined ? { askMessage: re.message } : {}) }, csignal ?? abortController.signal)
3215
+ .catch(() => ({ kind: "unavailable", cause: "error" }));
3216
+ if (verdict.kind === "allow")
3217
+ return { action: "allow", updatedInput: editArgs };
3218
+ if (verdict.kind === "block") {
3219
+ const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
3220
+ const category = verdict.category ? inlineUntrusted(verdict.category) : "";
3221
+ return {
3222
+ action: "deny",
3223
+ message: `auto-mode classifier blocked the approved edit at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
3224
+ decisionReason: "classifier",
3225
+ };
3226
+ }
3227
+ }
3098
3228
  const rr = await resolveAsk({
3099
3229
  toolName: creq.toolName,
3100
3230
  toolCallId: creq.toolCallId,
@@ -3116,8 +3246,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3116
3246
  pc.preToolUse === hooks?.preToolUse &&
3117
3247
  !preToolUseObservational &&
3118
3248
  pc.durableMandate !== true &&
3249
+ pc.contentMandate !== true &&
3119
3250
  askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
3120
- pc.hookEnv === hookEnvSource
3251
+ pc.hookEnv === hookEnvSource &&
3252
+ pc.autoMode?.decider === autoModeDecider
3121
3253
  ? { check: () => ({ action: "allow" }) }
3122
3254
  : policy !== undefined && pc.policy === policy
3123
3255
  ? {
@@ -3134,8 +3266,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3134
3266
  return first;
3135
3267
  if (first.action === "allow")
3136
3268
  return { action: "allow" };
3137
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
3138
- return first;
3269
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
3270
+ if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
3271
+ return first;
3272
+ return {
3273
+ action: "deny",
3274
+ message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
3275
+ `denied fail-closed (tighten-only)`,
3276
+ };
3139
3277
  }
3140
3278
  if (pc.durableMandate === true) {
3141
3279
  if (resolveCheckpointStore(spec, deps) !== undefined &&
@@ -3149,6 +3287,26 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3149
3287
  `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
3150
3288
  };
3151
3289
  }
3290
+ if (pc.autoMode !== undefined) {
3291
+ const verdict = await pc.autoMode.decider
3292
+ .decide({ req: creq, ...(first.message !== undefined ? { askMessage: first.message } : {}) }, csignal ?? abortController.signal)
3293
+ .catch(() => ({ kind: "unavailable", cause: "error" }));
3294
+ if (verdict.kind === "allow")
3295
+ return { action: "allow" };
3296
+ if (verdict.kind === "block") {
3297
+ const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
3298
+ const category = verdict.category ? inlineUntrusted(verdict.category) : "";
3299
+ return {
3300
+ action: "deny",
3301
+ message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
3302
+ decisionReason: "classifier",
3303
+ };
3304
+ }
3305
+ }
3306
+ if (sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
3307
+ recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
3308
+ return { action: "allow" };
3309
+ }
3152
3310
  const presentedArgs = creq.args;
3153
3311
  const askT0 = now();
3154
3312
  const resolved = await resolveAsk({
@@ -3173,7 +3331,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3173
3331
  if (resolved.action !== "allow")
3174
3332
  return resolved;
3175
3333
  if (resolved.updatedInput !== undefined) {
3176
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal);
3334
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider);
3177
3335
  }
3178
3336
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
3179
3337
  return { action: "allow" };
@@ -3193,8 +3351,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3193
3351
  }
3194
3352
  if (decision.action !== "ask")
3195
3353
  return decision;
3196
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
3197
- return decision;
3354
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
3355
+ if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
3356
+ return decision;
3357
+ return {
3358
+ action: "deny",
3359
+ message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
3360
+ `denied fail-closed (tighten-only)`,
3361
+ };
3198
3362
  }
3199
3363
  if (pc.durableMandate === true) {
3200
3364
  if (resolveCheckpointStore(spec, deps) !== undefined &&
@@ -3209,6 +3373,27 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3209
3373
  };
3210
3374
  }
3211
3375
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
3376
+ if (pc.autoMode !== undefined) {
3377
+ const verdict = await pc.autoMode.decider
3378
+ .decide({ req: { ...creq, args: presentedArgs }, ...(decision.message !== undefined ? { askMessage: decision.message } : {}) }, csignal ?? abortController.signal)
3379
+ .catch(() => ({ kind: "unavailable", cause: "error" }));
3380
+ if (verdict.kind === "allow") {
3381
+ return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
3382
+ }
3383
+ if (verdict.kind === "block") {
3384
+ const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
3385
+ const category = verdict.category ? inlineUntrusted(verdict.category) : "";
3386
+ return {
3387
+ action: "deny",
3388
+ message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
3389
+ decisionReason: "classifier",
3390
+ };
3391
+ }
3392
+ }
3393
+ if (sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
3394
+ recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
3395
+ return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
3396
+ }
3212
3397
  const askT0 = now();
3213
3398
  const resolved = await resolveAsk({
3214
3399
  toolName: creq.toolName,
@@ -3232,7 +3417,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3232
3417
  if (resolved.action !== "allow")
3233
3418
  return resolved;
3234
3419
  if (resolved.updatedInput !== undefined) {
3235
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal);
3420
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider);
3236
3421
  }
3237
3422
  if (pc.preToolUse === undefined) {
3238
3423
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
@@ -3257,6 +3442,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3257
3442
  const d = await layer.check(creq, csignal);
3258
3443
  if (d.updatedInput !== undefined)
3259
3444
  rewriteEmitterIndex.set(creq.toolCallId, idx);
3445
+ if (d.action === "ask") {
3446
+ recordFoldAskClass(creq.toolCallId, idx < callerSlotOffset ? policyAskClassOf(policy) : "mandate", idx < callerSlotOffset ? "caller_policy" : "ancestor_constraint");
3447
+ }
3260
3448
  return d;
3261
3449
  },
3262
3450
  }));
@@ -3607,6 +3795,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3607
3795
  inheritedGate: (() => {
3608
3796
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
3609
3797
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
3798
+ const constraintChain = (inheritedParentConstraints?.length ?? 0) > 0
3799
+ ? inheritedParentConstraints.map((pc) => constraintChainEntryOf(pc.policy, { ...(pc.autoMode !== undefined ? { autoModeArmed: true } : {}), ...(pc.durableMandate === true ? { durableMandate: true } : {}), ...(pc.contentMandate === true ? { contentMandate: true } : {}) }))
3800
+ : seedInheritedGate?.constraintChain;
3801
+ const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
3802
+ ? constraintChainDigest(constraintChain)
3803
+ : seedInheritedGate?.constraintDigest;
3610
3804
  return inheritedAncestorRules !== undefined ||
3611
3805
  inheritedShellGate !== undefined ||
3612
3806
  inheritedAdmittedOrgScopes !== undefined ||
@@ -3623,6 +3817,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3623
3817
  ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
3624
3818
  requiresParentConstraint,
3625
3819
  ...(requiresParentConstraint && parentConstraintCount !== undefined ? { parentConstraintCount } : {}),
3820
+ ...(requiresParentConstraint && constraintChain !== undefined
3821
+ ? { constraintChain: structuredClone(constraintChain), ...(constraintDigest !== undefined ? { constraintDigest } : {}) }
3822
+ : {}),
3626
3823
  }
3627
3824
  : undefined;
3628
3825
  })(),
@@ -3633,6 +3830,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3633
3830
  ...(announcedListingsRef.models !== undefined ? { models: [...announcedListingsRef.models] } : {}),
3634
3831
  }
3635
3832
  : undefined,
3833
+ delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
3636
3834
  });
3637
3835
  const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
3638
3836
  if (!checkpointStore)
@@ -3752,7 +3950,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3752
3950
  const mintedAt = Date.now();
3753
3951
  const cp = {
3754
3952
  token,
3755
- version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3953
+ version: f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3756
3954
  scope,
3757
3955
  sessionId,
3758
3956
  leafId,
@@ -3834,7 +4032,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3834
4032
  const mintedAt = Date.now();
3835
4033
  const cp = {
3836
4034
  token,
3837
- version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
4035
+ version: f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3838
4036
  scope,
3839
4037
  sessionId,
3840
4038
  leafId,
@@ -3983,6 +4181,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3983
4181
  }
3984
4182
  const parkedArgs = captured.value;
3985
4183
  const projectionMovedArgs = backendFidelity !== "structured-clone" && !samePlainValue(parked.value, parkedArgs);
4184
+ if (projectionMovedArgs && basePolicyForResumeEdit === undefined) {
4185
+ throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and no policy is wired to re-adjudicate the stored shape ` +
4186
+ `(no basePolicyForResumeEdit override and no caller toolPolicy) — refusing to file a row whose executable form nothing adjudicated`);
4187
+ }
3986
4188
  if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) {
3987
4189
  const presented = encodeAtFidelity(backendFidelity, parkedArgs);
3988
4190
  if (!presented.ok) {
@@ -3992,6 +4194,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3992
4194
  toolName: req.toolName,
3993
4195
  args: presented.value,
3994
4196
  toolCallId: req.toolCallId,
4197
+ budget: budgetSnapshot,
3995
4198
  ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}),
3996
4199
  }, abortController.signal));
3997
4200
  const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
@@ -4081,7 +4284,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4081
4284
  const mintedAt = Date.now();
4082
4285
  cp = {
4083
4286
  token,
4084
- version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
4287
+ version: f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
4085
4288
  scope,
4086
4289
  sessionId,
4087
4290
  leafId,
@@ -4220,11 +4423,27 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4220
4423
  }
4221
4424
  : {}),
4222
4425
  isMarkedUnresolvable: (toolCallId) => inheritedUnavailableAsks.has(toolCallId),
4426
+ ...(sandboxAdmissionArmed
4427
+ ? {
4428
+ sandboxAdmission: {
4429
+ askClassesOf: (toolCallId) => foldAskClasses.get(toolCallId),
4430
+ boundaryCapable: sandboxBoundaryCapable(e.toolName),
4431
+ onAdmitted: emitSandboxAdmitted,
4432
+ },
4433
+ }
4434
+ : {}),
4223
4435
  });
4224
4436
  }
4225
4437
  finally {
4226
4438
  inheritedUnavailableAsks.delete(e.toolCallId);
4227
4439
  inheritedAskGrants.delete(e.toolCallId);
4440
+ foldAskClasses.delete(e.toolCallId);
4441
+ }
4442
+ const ancestorAdmitted = ancestorSandboxAdmissions.get(e.toolCallId);
4443
+ ancestorSandboxAdmissions.delete(e.toolCallId);
4444
+ if (ancestorAdmitted !== undefined && !result.block && !result.suspend) {
4445
+ emitSandboxAdmitted({ toolCallId: e.toolCallId, toolName: e.toolName, askClasses: ancestorAdmitted, boundary: "sandbox_internal" });
4446
+ result.preToolContext.push(`permission.sandbox_admitted: this call's inherited pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
4228
4447
  }
4229
4448
  if (!result.block && !result.suspend && result.preToolContext.length > 0) {
4230
4449
  preToolContexts.set(e.toolCallId, result.preToolContext);