@sema-agent/core 7.41.0 → 7.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/agents/subagent.js +2 -0
  3. package/dist/core/agent-definition.d.ts +1 -1
  4. package/dist/core/allow-entry.d.ts +241 -0
  5. package/dist/core/allow-entry.js +340 -0
  6. package/dist/core/allow-rule-shadow.d.ts +18 -0
  7. package/dist/core/allow-rule-shadow.js +1 -0
  8. package/dist/core/ask-origin.d.ts +5 -5
  9. package/dist/core/ask-origin.js +1 -1
  10. package/dist/core/auto-mode.d.ts +3 -2
  11. package/dist/core/checkpoint-store.d.ts +11 -19
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/decision-receipt.d.ts +4 -2
  14. package/dist/core/decision-receipt.js +1 -1
  15. package/dist/core/engine-notice.d.ts +8 -5
  16. package/dist/core/gate-fold.js +1 -1
  17. package/dist/core/gate-lanes.d.ts +1 -49
  18. package/dist/core/gate-lanes.js +85 -223
  19. package/dist/core/governance-codes.d.ts +3 -3
  20. package/dist/core/governance-codes.js +3 -3
  21. package/dist/core/hooks.d.ts +13 -10
  22. package/dist/core/hooks.js +5 -3
  23. package/dist/core/mode-policy.d.ts +3 -3
  24. package/dist/core/mode-policy.js +2 -2
  25. package/dist/core/permission-rules.d.ts +24 -23
  26. package/dist/core/permission-rules.js +37 -33
  27. package/dist/core/reminder-disclosure.d.ts +53 -2
  28. package/dist/core/reminder-disclosure.js +49 -36
  29. package/dist/core/rule-lane-reads.d.ts +84 -0
  30. package/dist/core/rule-lane-reads.js +164 -0
  31. package/dist/core/runner/contracts.d.ts +4 -2
  32. package/dist/core/runner/gate-exit.d.ts +3 -3
  33. package/dist/core/runner/image.js +4 -3
  34. package/dist/core/runner/inline-image.d.ts +14 -0
  35. package/dist/core/runner/inline-image.js +26 -0
  36. package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
  37. package/dist/core/runner/permission-rule-lanes.js +1 -1
  38. package/dist/core/runner/prepare-ask-lane.d.ts +0 -19
  39. package/dist/core/runner/prepare-ask-lane.js +2 -2
  40. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  41. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  42. package/dist/core/runner/prepare-park-ask.js +3 -3
  43. package/dist/core/runner/prepare-policy-chain.d.ts +12 -0
  44. package/dist/core/runner/prepare-policy-chain.js +10 -9
  45. package/dist/core/runner/prepare-project-context.d.ts +13 -3
  46. package/dist/core/runner/prepare-project-context.js +5 -4
  47. package/dist/core/runner/prepare-task.js +8 -5
  48. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -3
  49. package/dist/core/runner/prepare-turn-wiring.js +19 -18
  50. package/dist/core/runner/run-leg.js +1 -1
  51. package/dist/core/runner/runtask.js +2 -1
  52. package/dist/core/runner-deps.d.ts +11 -0
  53. package/dist/core/skill-grant.d.ts +8 -47
  54. package/dist/core/skill-grant.js +15 -136
  55. package/dist/core/synthetic-read-verdict.d.ts +108 -0
  56. package/dist/core/synthetic-read-verdict.js +111 -0
  57. package/dist/core/task-spec.d.ts +18 -0
  58. package/dist/core/tool-name-readings.d.ts +1 -1
  59. package/dist/core/tool-name-readings.js +1 -1
  60. package/dist/core/tool-policy.d.ts +25 -24
  61. package/dist/core/tool-policy.js +26 -17
  62. package/dist/core/tool-spec.d.ts +7 -0
  63. package/dist/core/trace.d.ts +16 -0
  64. package/dist/index.d.ts +5 -1
  65. package/dist/index.js +4 -0
  66. package/dist/internal/harness-types.d.ts +1 -1
  67. package/dist/tools/fs/dir-entries.d.ts +69 -0
  68. package/dist/tools/fs/dir-entries.js +60 -0
  69. package/dist/tools/fs/fs-pdf.d.ts +7 -11
  70. package/dist/tools/fs/fs-pdf.js +5 -14
  71. package/dist/tools/fs/fs-read.d.ts +173 -2
  72. package/dist/tools/fs/fs-read.js +399 -286
  73. package/dist/tools/fs/image-admission.d.ts +51 -0
  74. package/dist/tools/fs/image-admission.js +36 -0
  75. package/dist/tools/fs/search.js +3 -7
  76. package/package.json +1 -1
  77. package/test/export-surface.snapshot.json +29 -1
  78. package/wire-changes/core@7.41.1.json +159 -0
  79. package/wire-changes/core@7.42.0.json +1107 -0
@@ -22,6 +22,7 @@ import { STALL_FIRST_BYTE_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCall
22
22
  import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
23
23
  import { OUTPUT_TOOL_NAME, SKILL_TOOL_NAME, createOutputTool } from "./synthetic-tools.js";
24
24
  import { skillGateWiring } from "../skill-grant.js";
25
+ import { AllowEntryTable, screenAllowedToolsValue } from "../allow-entry.js";
25
26
  import { compileOutputSchema } from "./strict-output-schema.js";
26
27
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
27
28
  import { foldAdmissionFreeze } from "../memory-admission.js";
@@ -279,6 +280,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
279
280
  const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
280
281
  const permissionModeSeat = refusePermissionModeDoors(spec);
281
282
  const outputStyleFrame = resolveOutputStyleFrame(spec.outputStyle);
283
+ const allowedTools = screenAllowedToolsValue(spec.allowedTools, "TaskSpec.allowedTools");
282
284
  const declaredModeAtDoor = (() => {
283
285
  const requested = modeRequestAfterResume(permissionModeSeat, recordedModeSeed(resume?.seed.inheritedGate), resume?.outcome);
284
286
  const ceiling = internals?.inheritedGate?.permissionModeCeiling?.current ?? internals?.inheritedGate?.permissionMode;
@@ -398,13 +400,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
398
400
  const { canonical: taskRootCanonical } = await resolveTaskRootCanonical({ env: executionEnv, given: taskRootFinal, onNotice: deps.onNotice, sessionId, runId });
399
401
  const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, fileHistoryFact, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, request: fileHistoryRequest, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController });
400
402
  const { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillGrants, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, bindPermissionModeSeat, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
403
+ const allowedToolTables = { request: new AllowEntryTable(), deployment: new AllowEntryTable() };
401
404
  const legContextOverlay = () => runnerSelf?.contextOverlayOf?.(harnessRef.current?.getModelRef() ?? resolvedRole.modelRef);
402
405
  let resolvedReadFace;
403
406
  let resolvedWriteFace;
404
407
  let readDenyAdditionsNormalized = [];
405
408
  let handsLessResolvedFace;
406
409
  let handsLessResolvedWriteFace;
407
- const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, skillGrantCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDeciderRef.current, autoModeDenialTracking: () => autoModeDenialTrackingRef.current, autoModeArming: () => autoModeArmingRef.current, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
410
+ const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, skillGrantCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, requestAllowedTools: allowedToolTables.request, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDeciderRef.current, autoModeDenialTracking: () => autoModeDenialTrackingRef.current, autoModeArming: () => autoModeArmingRef.current, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
408
411
  bindPermissionModeSeat(permissionMode);
409
412
  const announceRuleStoreSkipped = ruleStoreUnreadableNotice(deps.onNotice, { sessionId, runId });
410
413
  const persistedPathRowsRead = persistedPathRowsOfTask({ deps, principal: spec.principal, sessionId, root: taskRootFinal, home: executionEnv.homeDir, taskId: hostTaskId, handsEnabled, signals: [abortController.signal, ...(spec.signal !== undefined ? [spec.signal] : [])], onUnread: announceRuleStoreSkipped });
@@ -616,7 +619,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
616
619
  deps.onError?.(asThrownError(err), { phase: "config", sessionId });
617
620
  }
618
621
  }
619
- const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget: doors.skillListingBudget });
622
+ const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, allowedTools, allowedToolTables, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget: doors.skillListingBudget });
620
623
  const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, skillSpecs, skillSeatOf, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
621
624
  const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, dateAnchor, gitStatusRef } = await preparePromptInputs({ spec, deps, contextOverlay: legContextOverlay(), failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace: resolvedWriteFace?.inForce(), toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, session, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
622
625
  const { deferred, memoryBlock, toolSearch: toolSearchPolicy } = prepareDeferClassify({ spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
@@ -730,7 +733,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
730
733
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
731
734
  }
732
735
  }
733
- const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ 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, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
736
+ const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ 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: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
734
737
  const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: handsReadFace.shellReadStationArmed, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
735
738
  const writeProtectionCheck = writeProtection?.check;
736
739
  writeProtectionJudgeRef.current = writeProtection?.judgeTarget;
@@ -744,12 +747,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
744
747
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, skillGrantCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
745
748
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
746
749
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, skillSpecs, now, humanReviewRef, abortController, harness, pausedRef });
747
- const seatInputsFor = skillGateWiring({ tools, skillToolName: SKILL_TOOL_NAME, seatOf: skillSeatOf, mode: () => permissionMode.current, lane: skillGrantLane, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.skill_allowed", version: 1, taskId: hostTaskId, ...info, entries: [...info.entries], ts: Date.now() })) });
750
+ const seatInputsFor = skillGateWiring({ tools, skillToolName: SKILL_TOOL_NAME, seatOf: skillSeatOf, mode: () => permissionMode.current, lane: skillGrantLane, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.skill_allowed", version: 1, taskId: hostTaskId, ...info, entries: [...info.entries], ts: Date.now() })), allowEntries: { deploymentAllows: { ...allowEntryLanes.deployment, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "deployment", ...info, entries: [...info.entries], ts: Date.now() })) }, requestAllows: { ...allowEntryLanes.request, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "request", ...info, entries: [...info.entries], ts: Date.now() })) } } });
748
751
  const { approvedCallScreen } = prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, seatInputsFor, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
749
752
  const postureFrameRef = { report: (err) => deps.onError?.(err, { phase: "degraded", sessionId }) };
750
753
  const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas: rosterSeat, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, contextOverlay: legContextOverlay, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
751
754
  const preparedHolder = {};
752
- const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, skillGrants: skillGrantLane, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
755
+ const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, skillGrants: skillGrantLane, callerPolicy: lockedPreflight.toolPolicy, inheritedPolicies: (inheritedParentConstraints ?? []).map((c) => c.policy), persistedRuleLane: permissionRuleLane, orgRuleLane: permissionRuleOrgLane, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
753
756
  await onceLedger.settle(session, announcedListingsRef);
754
757
  const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: askSeatEffective(wiringManifest) }, ...(outputStyleFrame !== undefined ? { outputStyleFrame } : {}), permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, skillGrants: skillGrantLane, skillSpecs, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}), ...(approvedCallScreen !== undefined ? { approvedCallScreen } : {}) });
755
758
  rollback.commit();
@@ -6,7 +6,8 @@ import type { EpochDeclaredSections } from "../../prompt-assembly/epoch.js";
6
6
  import { type TurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
7
7
  import type { PromptTextDeclaration } from "../../prompts/default.js";
8
8
  import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
9
- import { type ToolPolicy } from "../tool-policy.js";
9
+ import type { ToolPolicy } from "../tool-policy.js";
10
+ import { type SyntheticReadJudge } from "../synthetic-read-verdict.js";
10
11
  import type { RunnerDeps, TaskSpec } from "../types.js";
11
12
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
12
13
  import { type ContainmentFace } from "../../tools/fs/index.js";
@@ -78,10 +79,19 @@ export interface PrepareTurnWiringInput {
78
79
  /** borrowed-mutable — the live read-file state, or undefined. Writers here, at RUN time: `detectExternalChanges` evicts a
79
80
  * not-found entry; `onCompactionApplied` applies a landed compaction to it. The hands phase mints it. */
80
81
  readFileStateForCheckpoint: ReadFileState | undefined;
81
- /** borrowed-readonly — the deny-narrowing fold, or undefined (one of the changed-files deny door's two authorities). */
82
+ /** borrowed-readonly — the deny-narrowing fold, or undefined (a layer of the synthetic-Read judge). */
82
83
  denyNarrowingPolicy: ToolPolicy | undefined;
83
- /** borrowed-readonly — the skill lane: the run's skill deny rows (the deny door's other authority; only `denying` is read). */
84
+ /** borrowed-readonly — the skill lane: the run's skill deny rows (a layer of the judge; only `denying` is read). */
84
85
  skillGrants: Prepared["skillGrants"];
86
+ /** borrowed-readonly — the deployment's caller policy as resolved through the lock layer, or undefined (a layer of the
87
+ * judge, consulted with an aborted signal — never an approver). */
88
+ callerPolicy: ToolPolicy | undefined;
89
+ /** borrowed-readonly — each live inherited parent policy (a layer of the judge, consulted like the caller policy). */
90
+ inheritedPolicies: readonly ToolPolicy[];
91
+ /** borrowed-readonly — the person's persisted-rule lane, or undefined (a layer of the judge). */
92
+ persistedRuleLane: Prepared["persistedRuleLane"];
93
+ /** borrowed-readonly — the org rule lane, or undefined (a layer of the judge). */
94
+ orgRuleLane: Prepared["permissionRuleOrg"];
85
95
  /** borrowed-readonly — the run's abort controller; only `signal` (the deny door's and the stat's signal). */
86
96
  abortController: Pick<AbortController, "signal">;
87
97
  /** borrowed-readonly — the per-task identity (the background listing's owner). */
@@ -142,6 +152,9 @@ export interface PrepareTurnWiringResult {
142
152
  onCompactionApplied: Prepared["onCompactionApplied"];
143
153
  /** owned — the boundary-time changed-files scan, or undefined without read state. */
144
154
  detectExternalChanges: Prepared["detectExternalChanges"];
155
+ /** owned — the run's ONE synthetic-Read deny judge over every layer that can refuse a model's `Read` (the
156
+ * changed-files door reads it; a face that delivers a file the model did not ask for reads the same one). */
157
+ syntheticReadVerdict: SyntheticReadJudge;
145
158
  /** owned — the post-compact background-task listing (`Prepared.listBackgroundTasks`). */
146
159
  listBackgroundTasks: Prepared["listBackgroundTasks"];
147
160
  /** owned — the compaction-boundary center adoption candidate, or undefined (no source, or no adoption). */
@@ -4,7 +4,8 @@ import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
4
4
  import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
5
5
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
6
6
  import { defaultTaskRegistry } from "../task-registry.js";
7
- import { refuseOutOfContractDecision } from "../tool-policy.js";
7
+ import { createSyntheticReadJudge } from "../synthetic-read-verdict.js";
8
+ import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
8
9
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
9
10
  import { applyContextShrinkToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
10
11
  import { resolveKey } from "../../tools/fs/safety.js";
@@ -12,7 +13,7 @@ import { PROMPT_HASH_SALT } from "./prompt-hash-salt.js";
12
13
  import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
13
14
  import { thrownText } from "../thrown-value.js";
14
15
  export function prepareTurnWiring(input) {
15
- const { deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, skillGrants, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder } = input;
16
+ const { deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, skillGrants, callerPolicy, inheritedPolicies, persistedRuleLane, orgRuleLane, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder } = input;
16
17
  const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
17
18
  const cacheFingerprint = cacheBreakDetector
18
19
  ? { systemPrompt: systemPromptSeat.current, tools: toolsToFingerprintInputs(harnessTools) }
@@ -101,19 +102,18 @@ export function prepareTurnWiring(input) {
101
102
  const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
102
103
  ? (attachedComplete, preserveReadState) => applyContextShrinkToReadFileState(readFileStateForCheckpoint, { kind: "summarized", attachedComplete, ...(preserveReadState !== undefined ? { preserveKeys: preserveReadState } : {}) })
103
104
  : undefined;
104
- const changedFilesReadDenied = async (path) => {
105
- const probe = { toolName: "Read", args: { file_path: path }, toolCallId: "changed-files-visibility-probe" };
106
- if (skillGrants.denying(probe) !== undefined)
107
- return true;
108
- if (denyNarrowingPolicy === undefined)
109
- return false;
110
- try {
111
- const d = refuseOutOfContractDecision(await denyNarrowingPolicy.check(probe, abortController.signal));
112
- return d.action === "deny";
113
- }
114
- catch {
115
- return false;
116
- }
105
+ const syntheticReadVerdict = createSyntheticReadJudge({
106
+ skillGrants,
107
+ ...(denyNarrowingPolicy !== undefined ? { denyNarrowingPolicy } : {}),
108
+ ...(callerPolicy !== undefined ? { callerPolicy } : {}),
109
+ inheritedPolicies,
110
+ ...(persistedRuleLane !== undefined ? { persistedRuleLane } : {}),
111
+ ...(orgRuleLane !== undefined ? { orgRuleLane } : {}),
112
+ signal: abortController.signal,
113
+ });
114
+ const changedFilesReadDenied = async (paths) => {
115
+ const deadlineAt = Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
116
+ return Promise.all(paths.map(async (path) => (await syntheticReadVerdict({ file_path: path }, "changed-files", { deadlineAt })).verdict === "deny"));
117
117
  };
118
118
  const detectExternalChanges = handsEnabled && readFileStateForCheckpoint
119
119
  ? async (maxFiles) => {
@@ -124,8 +124,9 @@ export function prepareTurnWiring(input) {
124
124
  .slice(0, Math.max(0, maxFiles));
125
125
  const changed = [];
126
126
  const evicted = [];
127
- for (const [path, entry] of candidates) {
128
- if (await changedFilesReadDenied(path))
127
+ const denied = await changedFilesReadDenied(candidates.map(([path]) => path));
128
+ for (const [i, [path, entry]] of candidates.entries()) {
129
+ if (denied[i] === true)
129
130
  continue;
130
131
  try {
131
132
  const info = await executionEnv.fileInfo(path, abortController.signal);
@@ -202,5 +203,5 @@ export function prepareTurnWiring(input) {
202
203
  const effectiveReadFaceObserved = carrierReadFace();
203
204
  const effectiveWriteFaceObserved = carrierWriteFace();
204
205
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
205
- return { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved };
206
+ return { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, syntheticReadVerdict, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved };
206
207
  }
@@ -2,7 +2,7 @@ import { snapshotActorAssertion } from "../../internal/llm.js";
2
2
  import { redactThenCut } from "../../agents/subagent-steps.js";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
4
  import { CheckpointError, readPendingSteerQueue, realApprovalOrgFact, remainingBudgetMicroUsd } from "../checkpoint-store.js";
5
- import { normalizePersistedRuleHit } from "../gate-lanes.js";
5
+ import { normalizePersistedRuleHit } from "../rule-lane-reads.js";
6
6
  import { decidersNote, SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
7
7
  import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
8
8
  import { buildAppliedHumanInputEvent, buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
@@ -1,4 +1,5 @@
1
1
  import { deliverEngineNotice } from "../types.js";
2
+ import { withDeploymentAllowedTools } from "../allow-entry.js";
2
3
  import { uuidv7 } from "../../internal/harness.js";
3
4
  import { snapshotActorAssertion } from "../../internal/llm.js";
4
5
  import { CheckpointError } from "../checkpoint-store.js";
@@ -77,7 +78,7 @@ export class Runner {
77
78
  assertNoRetiredRunnerDeps(deps);
78
79
  const runner = this;
79
80
  this.depsSeat = { get deps() { return runner.deps; } };
80
- this.deps = deps;
81
+ this.deps = withDeploymentAllowedTools(deps);
81
82
  if (this.deps.tiers) {
82
83
  this.deps = { ...this.deps, models: expandTiers(this.deps.models, this.deps.tiers) };
83
84
  }
@@ -609,6 +609,17 @@ export interface RunnerDeps {
609
609
  * adopted principal's bucket forever.
610
610
  */
611
611
  localOwnerRules?: boolean;
612
+ /**
613
+ * The DEPLOYMENT's allow rules — standing yeses its operator writes for every run of this Runner, in the grammar of
614
+ * {@link import("./task-spec.js").TaskSpec.allowedTools} (the same compiler, matcher and auto-mode strip). One of the allow
615
+ * layer's members, read beside the request's list, a person's stored rules and a skill's list under one predicate: deny
616
+ * and ask rules still win, a question no allow rule clears is still put to a person (the entry disclosed as the
617
+ * deployment's), and a call it clears is attributed to it (`decisionReason: "deployment_rule"`). Lives for the Runner —
618
+ * read once at construction: a value that is not a list of strings refuses construction (`config.invalid_permission_rules`,
619
+ * with its issues), and the entries that read as nothing are announced once, when the Runner is built
620
+ * (`permission.allowed_tool_not_granted`, `detail.entries[].source: "deployment"`).
621
+ */
622
+ allowedTools?: readonly string[];
612
623
  /**
613
624
  * design/182 §9 — DECLARE that this deployment drives cloud sync (`syncPermissionRules`) for the
614
625
  * wired rule store. Purely a disclosure input: it is reported as `permissionRules.syncWired` on the
@@ -1,9 +1,8 @@
1
1
  import type { PermissionResult, ToolCallRequest } from "./tool-policy.js";
2
- import { type PersistedRule } from "./permission-rule-model.js";
3
2
  import type { PermissionMode } from "./ask-origin.js";
4
3
  import { type PermissionSeatInputs, type PermissionSeatReading } from "./mode-policy.js";
5
4
  import type { AgentTool } from "../internal/harness-types.js";
6
- import type { EngineNotice } from "./engine-notice.js";
5
+ import { type AllowEntryGateSeats, type AllowEntryInertReason, type AllowEntryJudgeContext, type CompiledAllowEntry } from "./allow-entry.js";
7
6
  /** Who wrote a row: the `Skill` tool on a successful activation, prepare for a skill the person invoked by name, or a
8
7
  * delegated child's seed from its parent. */
9
8
  export declare const SKILL_GRANT_ORIGINS: readonly ["model", "user", "inherited"];
@@ -17,45 +16,20 @@ export interface SkillGrantRecord {
17
16
  readonly entry: string;
18
17
  readonly origin?: SkillGrantOrigin;
19
18
  }
20
- /** Why an entry grants (or refuses) nothing — the closed set the start-of-run notice names. */
21
- export declare const SKILL_ENTRY_INERT_REASONS: readonly ["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar"];
22
- export type SkillEntryInertReason = (typeof SKILL_ENTRY_INERT_REASONS)[number];
23
- /** One entry the grammar reads (kind closed): a whole non-shell tool; a protocol namespace; the whole shell tool; a shell
24
- * command rule (`interpreter` = an interpreter-headed open prefix); the Read directory form; a deny-side path pattern. */
25
- type CompiledEntry = {
26
- readonly kind: "tool";
27
- readonly name: string;
28
- } | {
29
- readonly kind: "namespace";
30
- readonly pattern: string;
31
- } | {
32
- readonly kind: "shell";
33
- readonly name: string;
34
- } | {
35
- readonly kind: "command";
36
- readonly rule: PersistedRule;
37
- readonly interpreter: boolean;
38
- } | {
39
- readonly kind: "read_dir";
40
- readonly rule: PersistedRule;
41
- } | {
42
- readonly kind: "path";
43
- readonly rule: PersistedRule;
44
- };
45
19
  /** One row of the table. */
46
20
  export interface SkillGrantRow {
47
21
  readonly skill: string;
48
22
  readonly origin: SkillGrantOrigin;
49
23
  readonly behavior: "allow" | "deny";
50
24
  readonly entry: string;
51
- readonly compiled: CompiledEntry;
25
+ readonly compiled: CompiledAllowEntry;
52
26
  }
53
27
  /** An entry the grammar reads as granting or refusing nothing. */
54
28
  export interface SkillInertEntry {
55
29
  readonly skill: string;
56
30
  readonly list: "allowed-tools" | "disallowed-tools";
57
31
  readonly entry: string;
58
- readonly reason: SkillEntryInertReason;
32
+ readonly reason: AllowEntryInertReason;
59
33
  }
60
34
  /** The two lists a skill declares (the shape of a `SkillSpec`, read by name). */
61
35
  export interface SkillGrantSource {
@@ -120,13 +94,6 @@ export declare function compileSkillGrant(skill: SkillGrantSource, origin: Skill
120
94
  rows: SkillGrantRow[];
121
95
  inert: SkillInertEntry[];
122
96
  };
123
- /** The call facts a content row is judged against — the persisted lane's own bases. */
124
- export interface SkillGrantJudgeContext {
125
- readonly root: string | undefined;
126
- readonly sessionId: string | undefined;
127
- readonly liveCwd: () => string | undefined;
128
- readonly home: string | undefined;
129
- }
130
97
  /** What spoke for a call: the skill and the entries it stands on. */
131
98
  export interface SkillGrantHit {
132
99
  readonly skill: string;
@@ -178,7 +145,7 @@ export declare function skillSeatReader(skills: readonly SkillGrantSource[]): Sk
178
145
  /**
179
146
  * The gate's seat inputs for one call, composed in ONE place: the mounted instance's static seat (`calledName`), the `Skill`
180
147
  * tool's per-call reader in the leg's live mode (only when the call's wire name is the engine's `Skill` — `skillToolName`),
181
- * and the run's skill rows with their attribution sink.
148
+ * the run's skill rows with their attribution sink, and the request's and the deployment's entry lists with theirs.
182
149
  */
183
150
  export declare function skillGateWiring(opts: {
184
151
  tools: readonly AgentTool[];
@@ -187,7 +154,9 @@ export declare function skillGateWiring(opts: {
187
154
  mode: () => PermissionMode | undefined;
188
155
  lane: SkillGrantLane;
189
156
  onAllowed: NonNullable<SkillGateSeats["skillGrants"]>["onAllowed"];
190
- }): (calledName: string, wireName: string) => PermissionSeatInputs & SkillGateSeats;
157
+ /** The two entry lists that are not a skill's (the request's, the deployment's), with their attribution sinks. */
158
+ allowEntries: AllowEntryGateSeats;
159
+ }): (calledName: string, wireName: string) => PermissionSeatInputs & SkillGateSeats & AllowEntryGateSeats;
191
160
  /**
192
161
  * Seed a task's table at prepare, before any call is gated: the rows from outside this leg's own writers (`inherited` — a
193
162
  * parent's capture, held until this child ends, and a resumed leg's frozen rows, which keep the word `model` where the
@@ -217,7 +186,7 @@ export declare class SkillGrantTable {
217
186
  /** The rows as portable records (a copy). */
218
187
  records(): readonly SkillGrantRecord[];
219
188
  /** The lane over this table, bound to the leg's call facts. */
220
- lane(ctx: SkillGrantJudgeContext): SkillGrantLane;
189
+ lane(ctx: AllowEntryJudgeContext): SkillGrantLane;
221
190
  }
222
191
  /** Read a delegated child's capture ({@link readCarriedRecords}): `undefined` refuses the delegation. */
223
192
  export declare function readSkillGrantCapture(snapshot: readonly unknown[] | undefined): readonly SkillGrantRecord[] | undefined;
@@ -227,13 +196,5 @@ export declare function frozenSkillGrants(table: Pick<SkillGrantTable, "records"
227
196
  /** Read a park's frozen section ({@link readCarriedRecords}): absent ⇒ none; `undefined` ⇒ damaged — the resume refuses
228
197
  * it before the claim and prepare never seeds from it. */
229
198
  export declare function readFrozenSkillGrants(section: unknown): readonly SkillGrantRecord[] | undefined;
230
- /** The start-of-run notice for entries that grant (or refuse) nothing. */
231
- export declare const SKILL_ENTRY_NOT_GRANTED_CODE = "skills.allowed_tool_not_granted";
232
199
  /** The refusal for a `TaskSpec.userInvokedSkills` name the run does not carry (or a value that is not a list of names). */
233
200
  export declare const USER_INVOKED_SKILL_UNKNOWN_CODE = "config.user_invoked_skill_unknown";
234
- /**
235
- * The notice for entries this grammar reads as nothing — one per prepared task, delivered only when one exists. The
236
- * deployment's operator owns the skill files; the run goes on (an entry that grants nothing makes it ask more).
237
- */
238
- export declare function skillEntriesNotGrantedNotice(inert: readonly SkillInertEntry[], sessionId: string): EngineNotice;
239
- export {};
@@ -1,15 +1,11 @@
1
- import { heldBy, wireOf } from "./tool-name-readings.js";
2
- import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
3
- import { protocolOf } from "./protocol-table.js";
4
- import { COARSE_SHELL_TOOLS } from "./tool-policy.js";
5
- import { COMMAND_RULE_TOOL, READ_RULE_TOOL, findAdmittingRule, laneToolNamesOf, parseRuleText, programRunReachOf, ruleToolGrammarOf } from "./permission-rule-model.js";
6
- import { pathRuleLaneAnswer } from "./runner/permission-rule-lanes.js";
1
+ import { wireOf } from "./tool-name-readings.js";
2
+ import { parsePermissionRule } from "./permission-rules.js";
7
3
  import { mountedToolPermissionSeat } from "./mode-policy.js";
8
4
  import { inlineUntrusted } from "./untrusted-text.js";
9
5
  import { boundInputHashOf } from "./canonical-json.js";
6
+ import { allowingRows, autoStripped, compileAllowEntry, compileAllowList, judgeAllowEntries } from "./allow-entry.js";
10
7
  export const SKILL_GRANT_ORIGINS = Object.freeze(["model", "user", "inherited"]);
11
8
  const FROZEN_SKILL_GRANT_ORIGINS = new Set(["model", "inherited"]);
12
- export const SKILL_ENTRY_INERT_REASONS = Object.freeze(["wildcard_all", "namespace_wildcard", "path_pattern", "domain", "content_unsupported", "grammar"]);
13
9
  export function skillNamedBy(args, skills) {
14
10
  const named = args?.skill;
15
11
  return typeof named === "string" ? skills.find((s) => s.name === named) : undefined;
@@ -47,50 +43,6 @@ export function detachSkillLists(skills) {
47
43
  export function declaresSkillLists(skill) {
48
44
  return (skill.allowedTools?.length ?? 0) > 0 || (skill.disallowedTools?.length ?? 0) > 0;
49
45
  }
50
- function ruleRowOf(parsed) {
51
- return { ...parsed, scope: { kind: "global" }, adds: [{ dot: { actor: "skill", counter: 1 }, origin: "user", createdAt: "" }] };
52
- }
53
- function compileEntry(raw, behavior) {
54
- const parsed = parsePermissionRule(raw);
55
- const name = parsed.toolName;
56
- if (name === "*")
57
- return "wildcard_all";
58
- if (parsed.ruleContent === undefined) {
59
- const ns = protocolOf(name);
60
- if (ns !== undefined) {
61
- if (name.slice(ns.prefix.length).split("__")[0]?.includes("*") === true)
62
- return "namespace_wildcard";
63
- if (isNamespacedCoveringRuleName(name))
64
- return { kind: "namespace", pattern: name };
65
- return { kind: "tool", name };
66
- }
67
- return COARSE_SHELL_TOOLS.includes(name) ? { kind: "shell", name } : { kind: "tool", name };
68
- }
69
- if (name === COMMAND_RULE_TOOL) {
70
- const allowRead = parseRuleText(raw, behavior);
71
- if ("rule" in allowRead)
72
- return { kind: "command", rule: ruleRowOf(allowRead.rule), interpreter: false };
73
- if (behavior === "allow" && allowRead.reject.code === "invalid.bare_interpreter_prefix") {
74
- const wide = parseRuleText(raw, "deny");
75
- if ("rule" in wide)
76
- return { kind: "command", rule: ruleRowOf({ ...wide.rule, behavior: "allow" }), interpreter: true };
77
- }
78
- return "grammar";
79
- }
80
- if (ruleToolGrammarOf(name) === "path") {
81
- const read = parseRuleText(raw, behavior);
82
- if ("rule" in read) {
83
- if (read.rule.match === "subpath" && read.rule.tool === READ_RULE_TOOL && behavior === "allow")
84
- return { kind: "read_dir", rule: ruleRowOf(read.rule) };
85
- if (behavior === "deny")
86
- return { kind: "path", rule: ruleRowOf(read.rule) };
87
- }
88
- return "path_pattern";
89
- }
90
- if (/^domain:/.test(parsed.ruleContent))
91
- return "domain";
92
- return "content_unsupported";
93
- }
94
46
  export function compileSkillGrant(skill, origin) {
95
47
  const rows = [];
96
48
  const inert = [];
@@ -98,79 +50,17 @@ export function compileSkillGrant(skill, origin) {
98
50
  ["allowed-tools", "allow", skill.allowedTools ?? []],
99
51
  ["disallowed-tools", "deny", skill.disallowedTools ?? []],
100
52
  ]) {
101
- const voided = behavior === "allow" && entries.some((e) => parsePermissionRule(e).toolName === "*");
102
- for (const entry of entries) {
103
- const compiled = voided ? "wildcard_all" : compileEntry(entry, behavior);
104
- if (typeof compiled === "string")
105
- inert.push({ skill: skill.name, list, entry, reason: compiled });
106
- else
107
- rows.push({ skill: skill.name, origin, behavior, entry, compiled });
108
- }
53
+ const compiled = compileAllowList(entries, behavior);
54
+ for (const r of compiled.rows)
55
+ rows.push({ skill: skill.name, origin, behavior, entry: r.entry, compiled: r.compiled });
56
+ for (const i of compiled.inert)
57
+ inert.push({ skill: skill.name, list, entry: i.entry, reason: i.reason });
109
58
  }
110
59
  return { rows, inert };
111
60
  }
112
- function standsDownIn(row, mode, req) {
113
- if (mode !== "auto")
114
- return false;
115
- if (row.compiled.kind === "shell" || (row.compiled.kind === "command" && row.compiled.interpreter))
116
- return true;
117
- return row.compiled.kind === "tool" && req !== undefined && answersTo(req, (n) => COARSE_SHELL_TOOLS.includes(n));
118
- }
119
- function answersTo(req, has) {
120
- return heldBy("skillGrant.names", { has }, req);
121
- }
122
- function nameRowReaches(compiled, req) {
123
- const covers = compiled.kind === "namespace" ? (n) => namespacedRuleNameCovers(compiled.pattern, n) : compiled.kind === "tool" || compiled.kind === "shell" ? (n) => n === compiled.name : undefined;
124
- return covers !== undefined && answersTo(req, covers);
125
- }
126
- function commandOf(req) {
127
- const tool = laneToolNamesOf(req).find((n) => ruleToolGrammarOf(n) === "command");
128
- const command = req.args?.command;
129
- return tool !== undefined && typeof command === "string" ? { tool, command } : undefined;
130
- }
131
61
  function judge(rows, req, behavior, ctx) {
132
- const live = rows.filter((r) => r.behavior === behavior);
133
- if (live.length === 0)
134
- return undefined;
135
- for (const row of live)
136
- if (nameRowReaches(row.compiled, req))
137
- return { skill: row.skill, entries: [row.entry] };
138
- const call = commandOf(req);
139
- const liveCwd = ctx.liveCwd();
140
- if (call !== undefined) {
141
- const byRule = new Map();
142
- for (const r of live)
143
- if (r.compiled.kind === "command")
144
- byRule.set(r.compiled.rule, r);
145
- if (behavior === "allow") {
146
- const covering = findAdmittingRule([...byRule.keys()], { tool: call.tool, command: call.command, cwd: ctx.root, ...(ctx.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}), ...(liveCwd !== undefined ? { execCwd: liveCwd } : {}) });
147
- const hits = (covering ?? []).flatMap((rule) => byRule.get(rule) ?? []);
148
- const first = hits[0];
149
- if (first !== undefined)
150
- return { skill: first.skill, entries: hits.map((r) => r.entry) };
151
- }
152
- else {
153
- for (const [rule, row] of byRule) {
154
- if (programRunReachOf(rule, call.command).reach !== "not_reached")
155
- return { skill: row.skill, entries: [row.entry] };
156
- }
157
- }
158
- }
159
- const pathKind = behavior === "allow" ? "read_dir" : "path";
160
- const pathRows = live.filter((r) => r.compiled.kind === pathKind);
161
- if (pathRows.length > 0) {
162
- const table = pathRows.map((r) => r.compiled.rule);
163
- for (const name of laneToolNamesOf(req).filter((n) => ruleToolGrammarOf(n) === "path")) {
164
- const answer = pathRuleLaneAnswer(table, { ...req, toolName: name }, { root: ctx.root, sessionId: ctx.sessionId, liveCwd, home: ctx.home });
165
- const spoke = answer !== undefined && ("unreadable" in answer ? behavior === "deny" : "behavior" in answer && answer.behavior === behavior);
166
- if (spoke) {
167
- const rule = answer !== undefined && "rules" in answer ? answer.rules[0]?.rule : undefined;
168
- const row = pathRows.find((r) => r.compiled.rule.rule === rule) ?? pathRows[0];
169
- return { skill: row.skill, entries: [row.entry] };
170
- }
171
- }
172
- }
173
- return undefined;
62
+ const hit = judgeAllowEntries(rows, req, behavior, ctx);
63
+ return hit === undefined || hit[0] === undefined ? undefined : { skill: hit[0].skill, entries: hit.map((r) => r.entry) };
174
64
  }
175
65
  export function skillDenyDecision(hit) {
176
66
  return {
@@ -195,7 +85,7 @@ const CARD_ENTRIES_MAX = 10;
195
85
  const CARD_ENTRY_MAX_CHARS = 120;
196
86
  export function skillActivationQuestion(skill, mode) {
197
87
  const head = `Execute skill: ${inlineUntrusted(skill.name, 200)}`;
198
- const granting = compileSkillGrant(skill, "model").rows.filter((r) => r.behavior === "allow" && !standsDownIn(r, mode));
88
+ const granting = compileSkillGrant(skill, "model").rows.filter((r) => r.behavior === "allow" && !(mode === "auto" && autoStripped(r.compiled, undefined)));
199
89
  if (granting.length === 0)
200
90
  return head;
201
91
  const shown = granting.slice(0, CARD_ENTRIES_MAX).map((r) => inlineUntrusted(r.entry, CARD_ENTRY_MAX_CHARS));
@@ -216,6 +106,7 @@ export function skillGateWiring(opts) {
216
106
  ...(mountedToolPermissionSeat(opts.tools, calledName) === "passthrough" ? { permissionSeat: "passthrough" } : {}),
217
107
  ...(opts.seatOf !== undefined && wireName === opts.skillToolName ? { permissionSeatOf: (args) => opts.seatOf?.(args, opts.mode()) } : {}),
218
108
  skillGrants,
109
+ ...opts.allowEntries,
219
110
  });
220
111
  }
221
112
  export function seedSkillGrants(table, opts) {
@@ -248,7 +139,7 @@ export class SkillGrantTable {
248
139
  }
249
140
  seed(records, origin) {
250
141
  for (const r of records) {
251
- const compiled = compileEntry(r.entry, r.behavior);
142
+ const compiled = compileAllowEntry(r.entry, r.behavior);
252
143
  if (typeof compiled === "string")
253
144
  continue;
254
145
  this.hold({ skill: r.skill, origin: r.origin === "model" ? "model" : origin, behavior: r.behavior, entry: r.entry, compiled });
@@ -259,7 +150,7 @@ export class SkillGrantTable {
259
150
  }
260
151
  lane(ctx) {
261
152
  return {
262
- allowing: (req, mode) => judge(this.rowsHeld.filter((r) => !standsDownIn(r, mode, req)), req, "allow", ctx),
153
+ allowing: (req, mode) => judge(allowingRows(this.rowsHeld, req, mode), req, "allow", ctx),
263
154
  denying: (req) => judge(this.rowsHeld, req, "deny", ctx),
264
155
  };
265
156
  }
@@ -276,7 +167,7 @@ function readCarriedRecords(raw, carrier) {
276
167
  const { skill, behavior, entry, origin } = r;
277
168
  if (typeof skill !== "string" || typeof entry !== "string" || (behavior !== "allow" && behavior !== "deny"))
278
169
  return undefined;
279
- if (typeof compileEntry(entry, behavior) === "string")
170
+ if (typeof compileAllowEntry(entry, behavior) === "string")
280
171
  return undefined;
281
172
  if (carrier === "capture") {
282
173
  out.push({ skill, behavior, entry });
@@ -298,16 +189,4 @@ export function frozenSkillGrants(table) {
298
189
  export function readFrozenSkillGrants(section) {
299
190
  return readCarriedRecords(section, "frozen");
300
191
  }
301
- export const SKILL_ENTRY_NOT_GRANTED_CODE = "skills.allowed_tool_not_granted";
302
192
  export const USER_INVOKED_SKILL_UNKNOWN_CODE = "config.user_invoked_skill_unknown";
303
- const NOTICE_ENTRIES_SHOWN = 5;
304
- export function skillEntriesNotGrantedNotice(inert, sessionId) {
305
- const shown = inert.slice(0, NOTICE_ENTRIES_SHOWN).map((e) => `${inlineUntrusted(e.skill, 80)}: ${e.list} "${inlineUntrusted(e.entry, 120)}" (${e.reason})`);
306
- const rest = inert.length - shown.length;
307
- return {
308
- code: SKILL_ENTRY_NOT_GRANTED_CODE,
309
- message: `${inert.length} skill list entr${inert.length === 1 ? "y grants" : "ies grant"} or refuse${inert.length === 1 ? "s" : ""} nothing — ${shown.join("; ")}${rest > 0 ? `; and ${rest} more` : ""}. ` +
310
- "An allowed-tools list holding `*` voids the list, `mcp__*` names no server, and a write-path or domain entry has no allow reading; name the tools (or `mcp__<server>`) instead.",
311
- detail: { sessionId, entries: inert.map((e) => ({ skill: e.skill, list: e.list, entry: e.entry, reason: e.reason })) },
312
- };
313
- }