@sema-agent/core 7.52.0 → 7.54.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.
- package/CHANGELOG.md +470 -0
- package/LICENSE +3 -1
- package/README.md +3 -1
- package/dist/agents/cross-session-envelope.d.ts +8 -0
- package/dist/agents/cross-session-envelope.js +6 -0
- package/dist/agents/cross-session-judge.d.ts +21 -1
- package/dist/agents/cross-session-judge.js +8 -0
- package/dist/agents/cross-session-ref.d.ts +5 -0
- package/dist/agents/cross-session-ref.js +1 -0
- package/dist/agents/org-delivery-gate.d.ts +122 -0
- package/dist/agents/org-delivery-gate.js +69 -0
- package/dist/agents/peer-held-queue.d.ts +9 -4
- package/dist/agents/peer-held-queue.js +6 -5
- package/dist/agents/peer-session-drain.d.ts +35 -6
- package/dist/agents/peer-session-drain.js +271 -182
- package/dist/agents/read-inbox-tool.d.ts +7 -0
- package/dist/agents/read-inbox-tool.js +5 -1
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +6 -5
- package/dist/agents/subagent.js +117 -83
- package/dist/agents/tool-filter.d.ts +25 -4
- package/dist/agents/tool-filter.js +13 -3
- package/dist/brain/anthropic.d.ts +6 -1
- package/dist/brain/anthropic.js +4 -4
- package/dist/brain/model-presets.js +2 -1
- package/dist/brain/route-adjudicator.d.ts +6 -0
- package/dist/brain/routing.d.ts +24 -1
- package/dist/brain/routing.js +54 -19
- package/dist/brain/timeout.js +5 -4
- package/dist/core/a2a.d.ts +5 -2
- package/dist/core/a2a.js +33 -14
- package/dist/core/allow-entry.d.ts +3 -1
- package/dist/core/allow-entry.js +3 -2
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +106 -50
- package/dist/core/auto-compaction.js +69 -53
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +60 -36
- package/dist/core/checkpoint-store.js +7 -6
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/compliance.js +3 -1
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +28 -6
- package/dist/core/effective-path-target.js +28 -2
- package/dist/core/engine-notice.d.ts +47 -4
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +13 -4
- package/dist/core/gate-lanes.js +41 -34
- package/dist/core/gate-outcome.d.ts +18 -9
- package/dist/core/governance-codes.d.ts +9 -2
- package/dist/core/governance-codes.js +9 -0
- package/dist/core/hands-band-screen.d.ts +7 -0
- package/dist/core/hands-band-screen.js +32 -0
- package/dist/core/hooks.d.ts +21 -19
- package/dist/core/hooks.js +6 -5
- package/dist/core/locked-config.d.ts +110 -31
- package/dist/core/locked-config.js +155 -42
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-probe.js +1 -1
- package/dist/core/mcp-server-spec.d.ts +152 -17
- package/dist/core/mcp-server-spec.js +223 -40
- package/dist/core/mcp.d.ts +21 -14
- package/dist/core/mcp.js +59 -54
- package/dist/core/memory-engine/engine.d.ts +14 -19
- package/dist/core/memory-engine/engine.js +9 -6
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +16 -26
- package/dist/core/mode-policy.js +5 -18
- package/dist/core/mods/kernel/dispatch.js +13 -15
- package/dist/core/mods/kernel/events.d.ts +9 -6
- package/dist/core/mods/kernel/events.js +2 -2
- package/dist/core/mods/kernel/receive.d.ts +41 -11
- package/dist/core/mods/kernel/receive.js +124 -54
- package/dist/core/mods/kernel/site-rules.d.ts +80 -15
- package/dist/core/mods/kernel/site-rules.js +232 -40
- package/dist/core/mods/stations.d.ts +0 -6
- package/dist/core/mods/stations.js +0 -7
- package/dist/core/mounted-mcp.d.ts +57 -0
- package/dist/core/mounted-mcp.js +71 -2
- package/dist/core/own-property-receipt.d.ts +13 -0
- package/dist/core/own-property-receipt.js +3 -1
- package/dist/core/parent-spec-seat.d.ts +12 -15
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +87 -15
- package/dist/core/permission-rule-model.js +87 -8
- package/dist/core/permission-rule-org.d.ts +200 -33
- package/dist/core/permission-rule-org.js +217 -91
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/permission-rules.d.ts +7 -1
- package/dist/core/permission-rules.js +37 -8
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/retention.js +7 -2
- package/dist/core/retired-keys.d.ts +8 -2
- package/dist/core/retired-keys.js +12 -1
- package/dist/core/roles.js +60 -10
- package/dist/core/rule-lane-reads.js +1 -1
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +34 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +77 -34
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/mcp-redial.d.ts +52 -10
- package/dist/core/runner/mcp-redial.js +38 -9
- package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
- package/dist/core/runner/permission-rule-lanes.js +22 -10
- package/dist/core/runner/person-path-rows.d.ts +5 -3
- package/dist/core/runner/person-path-rows.js +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
- package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +58 -15
- package/dist/core/runner/prepare-config-doors.js +167 -146
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-defer-classify.js +4 -7
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +8 -7
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +3 -2
- package/dist/core/runner/prepare-listings.d.ts +1 -1
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
- package/dist/core/runner/prepare-memory-engine-session.js +19 -6
- package/dist/core/runner/prepare-memory.d.ts +3 -2
- package/dist/core/runner/prepare-memory.js +6 -2
- package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
- package/dist/core/runner/prepare-offload-wrappers.js +2 -1
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +14 -19
- package/dist/core/runner/prepare-project-context.d.ts +1 -1
- package/dist/core/runner/prepare-project-context.js +6 -4
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-assembly.js +20 -16
- package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
- package/dist/core/runner/prepare-prompt-inputs.js +6 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
- package/dist/core/runner/prepare-protocol-tools.js +19 -12
- package/dist/core/runner/prepare-question-face.d.ts +4 -2
- package/dist/core/runner/prepare-question-face.js +4 -3
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +22 -19
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
- package/dist/core/runner/prepare-wiring-manifest.js +9 -6
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.d.ts +4 -1
- package/dist/core/runner/resume-admission.js +16 -14
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
- package/dist/core/runner/resume-internals-and-config.js +3 -8
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/retired-runner-deps.d.ts +5 -6
- package/dist/core/runner/retired-runner-deps.js +1 -4
- package/dist/core/runner/run-leg.js +97 -43
- package/dist/core/runner/run-recovery-lanes.js +5 -3
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +21 -9
- package/dist/core/runner/run-turn-boundary.js +9 -3
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/skills-seat.d.ts +15 -0
- package/dist/core/runner/skills-seat.js +57 -0
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
- package/dist/core/runner/sub-agent-deny-lists.js +7 -5
- package/dist/core/runner/synthetic-tools.d.ts +5 -2
- package/dist/core/runner/synthetic-tools.js +2 -12
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +37 -22
- package/dist/core/session-policy-store.d.ts +19 -0
- package/dist/core/session-policy-store.js +22 -2
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +639 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +44 -6
- package/dist/core/task-notification.js +2 -2
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +7 -7
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +67 -23
- package/dist/core/tool-policy.js +72 -60
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +23 -0
- package/dist/core/untrusted-text.js +10 -10
- package/dist/core/user-attachment.d.ts +56 -2
- package/dist/core/user-attachment.js +139 -56
- package/dist/core/wiring-manifest.d.ts +4 -2
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.d.ts +10 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/loop/agent-loop.d.ts +12 -1
- package/dist/engine/loop/agent-loop.js +17 -5
- package/dist/engine/loop/types.d.ts +7 -1
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +2 -2
- package/dist/index.d.ts +37 -17
- package/dist/index.js +31 -14
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/goal.d.ts +24 -1
- package/dist/orchestration/goal.js +3 -2
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/artifact-store.d.ts +44 -9
- package/dist/prompt-assembly/artifact-store.js +38 -12
- package/dist/prompt-assembly/artifact.d.ts +12 -11
- package/dist/prompt-assembly/artifact.js +52 -21
- package/dist/prompt-assembly/assemble.d.ts +2 -0
- package/dist/prompt-assembly/assemble.js +2 -1
- package/dist/prompt-assembly/catalog/index.js +2 -1
- package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
- package/dist/prompt-assembly/catalog/memory.js +6 -0
- package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/composer.d.ts +2 -0
- package/dist/prompt-assembly/composer.js +1 -1
- package/dist/prompt-assembly/overlay.d.ts +21 -11
- package/dist/prompt-assembly/overlay.js +57 -19
- package/dist/prompt-assembly/select.d.ts +21 -6
- package/dist/prompt-assembly/select.js +41 -4
- package/dist/prompts/default.d.ts +1 -12
- package/dist/prompts/default.js +0 -3
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/stores/file/permission-rule-store.d.ts +1 -1
- package/dist/stores/file/permission-rule-store.js +2 -1
- package/dist/tools/artifact/artifact-tool.js +14 -3
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5651 -5457
- package/wire-changes/core@7.53.0.json +1968 -0
- package/wire-changes/core@7.54.0.json +2870 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { canonicalize } from "./canonical-json.js";
|
|
2
|
+
import { licenseCovers } from "./tool-policy.js";
|
|
3
|
+
import { parkedSkillApprovalRefusal } from "./skill-grant.js";
|
|
4
|
+
import { toolIdentityOf } from "./tool-roster.js";
|
|
5
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
|
+
export const APPROVAL_TARGET_ROLES = Object.freeze(["protective", "declared"]);
|
|
7
|
+
export function approvalObjectOf(row) {
|
|
8
|
+
const { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, targets, boundGrantDigest } = row;
|
|
9
|
+
const carried = [capabilityId, contractId, shapeDigest, implementationRevision].some((m) => m !== undefined);
|
|
10
|
+
return {
|
|
11
|
+
...(carried ? { tool: { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget } } : {}),
|
|
12
|
+
...(targets !== undefined ? { targets } : {}),
|
|
13
|
+
...(boundGrantDigest !== undefined ? { grants: boundGrantDigest } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function approvalTargetsOf(reading) {
|
|
17
|
+
const targets = {};
|
|
18
|
+
for (const role of APPROVAL_TARGET_ROLES) {
|
|
19
|
+
const view = reading?.[role];
|
|
20
|
+
if (view?.kind === "path" && (view.canonical !== undefined || view.unread === true))
|
|
21
|
+
targets[role] = view.canonical ?? null;
|
|
22
|
+
}
|
|
23
|
+
return Object.keys(targets).length > 0 ? { targets } : {};
|
|
24
|
+
}
|
|
25
|
+
export const APPROVAL_STATION_MEMBERS = Object.freeze({
|
|
26
|
+
claim: Object.freeze(["grants"]),
|
|
27
|
+
session: Object.freeze(["tool"]),
|
|
28
|
+
execution: Object.freeze(["tool", "grants", "targets"]),
|
|
29
|
+
});
|
|
30
|
+
const IDENTITY_MEMBERS = Object.freeze(["capabilityId", "contractId", "shapeDigest", "implementationRevision"]);
|
|
31
|
+
const shown = (s) => `"${inlineUntrusted(s, 240)}"`;
|
|
32
|
+
const vetoed = (name, why) => `The approved tool call "${name}" was not executed: ${why}, so the approval does not cover it (resume.target_moved). This approval is spent — the call has to be re-issued: its new card shows what it would act on now.`;
|
|
33
|
+
function faceKey(family, pt) {
|
|
34
|
+
const slot = pt === undefined ? null : { param: pt.param, access: pt.access, aliases: [...(pt.aliases ?? [])], base: pt.base ?? "cwd", absent: pt.absent ?? "none", patternParam: pt.patternParam ?? null, skillScopeEligible: pt.skillScopeEligible === true };
|
|
35
|
+
return canonicalize({ family: family ?? null, pathTarget: slot });
|
|
36
|
+
}
|
|
37
|
+
function judgeTool(row, live) {
|
|
38
|
+
const entry = live.tool;
|
|
39
|
+
if (entry === undefined)
|
|
40
|
+
return { member: "tool", disposition: "reopen", code: "resume.tool_unavailable", message: `the approved tool "${row.toolName}" is no longer available on resume — refusing to continue as if it ran; the checkpoint is reopened for a retry with the tool present` };
|
|
41
|
+
const bound = approvalObjectOf(row).tool;
|
|
42
|
+
if (bound === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
const id = toolIdentityOf(entry);
|
|
45
|
+
const differ = IDENTITY_MEMBERS.filter((k) => bound[k] !== id[k]);
|
|
46
|
+
if (differ.length > 0) {
|
|
47
|
+
const named = differ.map((k) => (bound[k] === undefined ? `${k} (missing on the row)` : k)).join(", ");
|
|
48
|
+
return { member: "tool", disposition: "reopen", code: "resume.tool_contract_mismatch", message: `the approved tool "${row.toolName}" on this leg is not the tool the approval was minted against (${named} differ) — refusing to execute a replacement wearing the name; the checkpoint is reopened for a retry with the original tool present` };
|
|
49
|
+
}
|
|
50
|
+
if (faceKey(bound.family, bound.pathTarget) === faceKey(entry.family, entry.pathTarget))
|
|
51
|
+
return undefined;
|
|
52
|
+
return { member: "tool", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the tool's face (its display family or the path target it declares) changed after the approval") };
|
|
53
|
+
}
|
|
54
|
+
function judgeTargets(row, live) {
|
|
55
|
+
const bound = row.targets;
|
|
56
|
+
if (bound === undefined || ("decided" in live && !licenseCovers(row.boundInputHash, live.decided)))
|
|
57
|
+
return undefined;
|
|
58
|
+
const roles = typeof bound === "object" && bound !== null && !Array.isArray(bound) ? Object.entries(bound) : [];
|
|
59
|
+
const whole = roles.length > 0 && roles.every(([k, v]) => APPROVAL_TARGET_ROLES.includes(k) && (v === null || (typeof v === "string" && v.length > 0)));
|
|
60
|
+
if (!whole)
|
|
61
|
+
return { member: "targets", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the target member of the approval on its checkpoint row is damaged (not a reading → resolved location record)") };
|
|
62
|
+
const now = approvalTargetsOf(live.targetReading).targets ?? {};
|
|
63
|
+
const where = (p) => (p === null ? "did not resolve" : `resolved to ${shown(p)}`);
|
|
64
|
+
for (const [role, was] of roles) {
|
|
65
|
+
const is = now[role];
|
|
66
|
+
if (is === was)
|
|
67
|
+
continue;
|
|
68
|
+
return { member: "targets", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, is === undefined ? `where its target resolves (it ${where(was)} when the card was asked) could not be established on this leg` : `its target ${is === null ? "does not resolve" : `resolves to ${shown(is)}`} now and ${where(was)} when the card was asked`) };
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
function judgeGrants(row, live, station) {
|
|
73
|
+
const refusal = parkedSkillApprovalRefusal(row, live.args, live.skills ?? [], live.skillToolName ?? "", station);
|
|
74
|
+
if (refusal === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
return station === "claim" ? { member: "grants", disposition: "refuse_pending", code: "checkpoint.invalid_outcome", message: refusal } : { member: "grants", disposition: "veto", message: refusal };
|
|
77
|
+
}
|
|
78
|
+
export function judgeApprovalObject(row, live, station) {
|
|
79
|
+
for (const member of APPROVAL_STATION_MEMBERS[station]) {
|
|
80
|
+
const miss = member === "tool" ? judgeTool(row, live) : member === "targets" ? judgeTargets(row, live) : judgeGrants(row, live, station === "claim" ? "claim" : "execution");
|
|
81
|
+
if (miss !== undefined)
|
|
82
|
+
return miss;
|
|
83
|
+
}
|
|
84
|
+
return "match";
|
|
85
|
+
}
|
|
@@ -2,8 +2,9 @@ import { type Egress } from "./untrusted-egress.js";
|
|
|
2
2
|
/** A SHORT, SECRET-SCRUBBED primary-arg summary. Abbreviation REDUCES the surface; the `same-principal` tier's pass
|
|
3
3
|
* is the leak boundary, on whichever field the summary came from (the value is PERSISTED + SSE-forwarded and may be
|
|
4
4
|
* read by a platform operator in a multi-tenant deployment). Rules:
|
|
5
|
-
* • command → the command NAME
|
|
6
|
-
*
|
|
5
|
+
* • command → the command NAME: the first segment's written command word past its control words and the
|
|
6
|
+
* assignments bash strips (bail to `undefined` on an unparseable text, a lone assignment, an assignment-shaped
|
|
7
|
+
* or computed command word; basename a path-form command; `+ …` if more follows).
|
|
7
8
|
* • file path → BASENAME only (no host directory layout).
|
|
8
9
|
* • url → `new URL` origin+pathname (drops `user:pass@` userinfo + query/fragment).
|
|
9
10
|
* • else → pattern/query/description, else the first string field whose name is no credential name.
|
package/dist/core/arg-summary.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readProgramPosition } from "../tools/fs/bash-program-position.js";
|
|
2
|
+
import { argvOf, lexShell } from "../shell/lex.js";
|
|
2
3
|
import { isSensitiveRecordKey } from "./credential-vocabulary.js";
|
|
3
4
|
import { egressText } from "./untrusted-egress.js";
|
|
4
5
|
const ACTIVITY_ARG_MAX = 80;
|
|
@@ -17,17 +18,22 @@ export function primaryActivityArg(args) {
|
|
|
17
18
|
const path = str("file_path") ?? str("filePath") ?? str("path") ?? str("notebook_path");
|
|
18
19
|
const url = str("url");
|
|
19
20
|
if (command !== undefined) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const lexed = lexShell(command);
|
|
22
|
+
if ("parseFailure" in lexed)
|
|
23
|
+
return undefined;
|
|
24
|
+
const named = lexed.segments.map((segment) => {
|
|
25
|
+
const argv = argvOf(segment);
|
|
26
|
+
const pos = readProgramPosition(argv);
|
|
27
|
+
return { segment, word: pos.written === undefined ? undefined : argv[pos.written + pos.assignments.length] };
|
|
28
|
+
}).find((s) => s.word !== undefined);
|
|
29
|
+
if (named?.word === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const word = named.word;
|
|
32
|
+
if (word.assignment !== undefined || word.expansions.length > 0 || word.unresolved !== undefined)
|
|
28
33
|
return undefined;
|
|
29
|
-
const name = basename(
|
|
30
|
-
|
|
34
|
+
const name = basename(word.folded);
|
|
35
|
+
const more = named.segment.tokens.indexOf(word) < named.segment.tokens.length - 1 || lexed.segments.slice(lexed.segments.indexOf(named.segment) + 1).some((s) => s.tokens.length > 0);
|
|
36
|
+
val = more ? `${name} …` : name;
|
|
31
37
|
}
|
|
32
38
|
else if (path !== undefined) {
|
|
33
39
|
val = basename(path);
|
|
@@ -113,8 +113,8 @@ export interface AskOriginFacts {
|
|
|
113
113
|
/** Which of the gate's OWN post-fold stations minted the standing ask, if one did — again the gate's
|
|
114
114
|
* record of its own layer (a station replaces an `allow` wholesale, so at most one fires per pass),
|
|
115
115
|
* never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. `unstated` is
|
|
116
|
-
* the last station: the mode's answer for a call to a tool that states no permission of its own when no
|
|
117
|
-
*
|
|
116
|
+
* the last station: the mode's answer for a call to a tool that states no permission of its own when no answer
|
|
117
|
+
* covers the fold's allow — its origin is `policy` (no tighten word), its class `unstated`. */
|
|
118
118
|
readonly tightened: "shell_gate" | "safety" | "unstated" | undefined;
|
|
119
119
|
/** The MANDATE mark inputs of this call — the tool SEAT's marks plus the gate's own per-call records
|
|
120
120
|
* ({@link MandateMarks}), read from the same two places the lane's own derivation reads them. The
|
|
@@ -191,19 +191,15 @@ export type RuleOffersAbsence = (typeof RULE_OFFERS_ABSENCES)[number];
|
|
|
191
191
|
* the reason is that a RULE is already speaking, not that the question is beyond rules, and a surface
|
|
192
192
|
* that auto-answers by posture is not crossing a mandate when it answers one.
|
|
193
193
|
*
|
|
194
|
-
*
|
|
195
|
-
* the wrong one is how this boundary drifts:
|
|
194
|
+
* THREE named readings of the one pass, because three callers need three different things and taking
|
|
195
|
+
* the wrong one is how this boundary drifts. None is the ENFORCEMENT reading: whether an allow rule clears the ask is the
|
|
196
|
+
* class row's `ruleMayClear` ({@link classifyAsk} — every door below is a class there), read once by the gate's allow layer.
|
|
196
197
|
* · `word` — the DISCLOSED reason ({@link RuleOffersAbsence}), first door wins, in the display order the card has always
|
|
197
198
|
* used (governance, the outranked allow, the hook, the ask rule, the two ancestor doors, then the marks). Display
|
|
198
199
|
* only. A structural door the offer factory owns (no lane wired, a tool it does not speak for, a
|
|
199
200
|
* task that cannot hold a rule) is decided BEFORE this and is deliberately silent;
|
|
200
201
|
* · `mandated` — the FACT the ask carries as its own member. Read off the whole mandate set, so which
|
|
201
202
|
* door is DISPLAYED cannot change it: an ask that is both outranked and mandated is mandated;
|
|
202
|
-
* · `ruleMayClear` — the ENFORCEMENT reading the gate's clearing arms take. It is `mandated` plus the
|
|
203
|
-
* person's explicit `ask` rule, and deliberately NOT `word !== undefined`: `allowRuleShadowed`
|
|
204
|
-
* is an ACCOUNT of a refusal that already happened, display-only by contract, and reading it as a
|
|
205
|
-
* blocker would let a policy forwarding that metadata refuse a call nothing is blocking. A
|
|
206
|
-
* display word is never an authority;
|
|
207
203
|
* · `mandate` — WHICH table word the bit stands on, only when the MARKS family is its whole reason (no
|
|
208
204
|
* governance, hook or ancestor door, and no bit stamped without a word); absent, the bit's reason is
|
|
209
205
|
* not one word.
|
|
@@ -211,7 +207,6 @@ export type RuleOffersAbsence = (typeof RULE_OFFERS_ABSENCES)[number];
|
|
|
211
207
|
export declare function ruleClearanceOf(ask: RuleClearanceFacts | undefined, marks: Omit<MandateMarks, "probeMandated">): {
|
|
212
208
|
word?: Exclude<RuleOffersAbsence, "lane_cannot_speak">;
|
|
213
209
|
mandated: boolean;
|
|
214
|
-
ruleMayClear: boolean;
|
|
215
210
|
mandate?: PersistedRuleMandate;
|
|
216
211
|
};
|
|
217
212
|
/** The facts one ask carries about whether a rule could clear it — the members {@link ruleClearanceOf}
|
|
@@ -235,6 +230,8 @@ export interface RuleClearanceFacts {
|
|
|
235
230
|
/** The engine-stamped probe mandate (the reversibility probe declared this call's demotion
|
|
236
231
|
* structural — for the built-in shell probe, a read outside the session's allowed directories). */
|
|
237
232
|
probeMandated?: boolean;
|
|
233
|
+
/** The ask stands on the call's cross-boundary obligation (see {@link import("./tool-policy.js").PermissionResult}). */
|
|
234
|
+
crossBoundaryMandated?: boolean;
|
|
238
235
|
/** An ancestor's approver answered "unavailable" for this call, or a durable mandate was floated here. */
|
|
239
236
|
inheritedUnresolved?: boolean;
|
|
240
237
|
/** The ask is being resolved at an ANCESTOR's frozen approver (the three inherited-lane mint sites
|
|
@@ -309,8 +306,11 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
|
|
|
309
306
|
* re-authorizes the agent, so the question is a property of the TARGET, not of anyone's hesitation.
|
|
310
307
|
* - `target_unresolved` — the reading of a written target could not say where it leads on disk:
|
|
311
308
|
* `probe_unanswered`'s doctrine one layer over.
|
|
309
|
+
* - `cross_boundary_taint` — the call's cross-boundary obligation is a person's confirmation (an org-side write from a tree
|
|
310
|
+
* that executes on a device of a multi-tenant deployment, or a device-bound call after an off-target read): a fact of
|
|
311
|
+
* the tree, not of the tool; read off the ask's own `crossBoundaryMandated` ({@link ruleClearanceOf}).
|
|
312
312
|
*/
|
|
313
|
-
export declare const PERSISTED_RULE_MANDATES: readonly ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "target_unresolved"];
|
|
313
|
+
export declare const PERSISTED_RULE_MANDATES: readonly ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "target_unresolved", "cross_boundary_taint"];
|
|
314
314
|
export type PersistedRuleMandate = (typeof PERSISTED_RULE_MANDATES)[number];
|
|
315
315
|
/** Membership test for {@link PersistedRuleMandate} — the screen a row reader applies to a stored word. */
|
|
316
316
|
export declare function isPersistedRuleMandate(v: unknown): v is PersistedRuleMandate;
|
|
@@ -323,6 +323,7 @@ declare const MANDATE_DISCLOSURE: {
|
|
|
323
323
|
readonly tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)";
|
|
324
324
|
readonly write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule";
|
|
325
325
|
readonly target_unresolved: "where this write leads on disk could not be determined (a link on the way could not be examined, or the environment could not resolve the target), so whether it lands on a guarded path is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can";
|
|
326
|
+
readonly cross_boundary_taint: "this run executes on a device outside the deployment's trust boundary and the deployment is multi-tenant, so an org-side write — or, after an off-target read, a call bound for the device — is confirmed by the deployment's approver for this call: no standing allow rule and no automatic lane answers for it, and an allow the configuration gives (a blanket seat or a policy's answer) is refused";
|
|
326
327
|
};
|
|
327
328
|
/** The fence over the disclosure table: `never` while every word has a row. */
|
|
328
329
|
export type DisclosureTableCoversEveryMandate = AssertAllKeysHandled<Exclude<PersistedRuleMandate, keyof typeof MANDATE_DISCLOSURE>>;
|
|
@@ -331,10 +332,9 @@ export type DisclosureTableCoversEveryMandate = AssertAllKeysHandled<Exclude<Per
|
|
|
331
332
|
export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | undefined): string;
|
|
332
333
|
/**
|
|
333
334
|
* The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
|
|
334
|
-
* the
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
* bit, which the org layer stamps):
|
|
335
|
+
* the WORD a mandated ask discloses for its marks (the class derivation reads the same marks as classes,
|
|
336
|
+
* and the allow layer reads the classes). Allow rules silence the classifier's questions, never a
|
|
337
|
+
* mandated one; this is the mark family's word for that boundary:
|
|
338
338
|
* · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
|
|
339
339
|
* (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
|
|
340
340
|
* are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
|
|
@@ -355,10 +355,11 @@ export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | unde
|
|
|
355
355
|
* in {@link irreversibilityMandateOf} which {@link safetyAxisOf} shares: such a write is not an irreversible action, and
|
|
356
356
|
* its park stays budgetable.
|
|
357
357
|
*
|
|
358
|
-
* Two consumers, one derivation: the
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
358
|
+
* Two consumers, one derivation: the ask's stamped `mandated` / `mandate` members and the runner's suggestion factory (a
|
|
359
|
+
* mandated ask offers no "stop asking me this" option — a rule minted from it would never clear it). Every word here is also a
|
|
360
|
+
* class the allow layer reads, so an ask this derivation mandates is never cleared by a rule; the converse does not hold — a
|
|
361
|
+
* class with no word here (a caller policy's own guard on an ask without the real-approval bit) leaves the ask unmandated, and its
|
|
362
|
+
* card can offer a rule the allow layer then discloses as outranked. The per-call member reaches both
|
|
362
363
|
* the same way every other per-call fact does: the gate stamps it on the surviving ask, the
|
|
363
364
|
* synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
|
|
364
365
|
*/
|
|
@@ -465,8 +466,10 @@ export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
|
|
|
465
466
|
* `requires_user_interaction` is the SERVER's declaration on the called tool (`_meta["anthropic/requiresUserInteraction"] === true`): a
|
|
466
467
|
* permission question, unlike `content_question` (the reserved question tool's content-routing signal) — it carries that class's arbitration
|
|
467
468
|
* row cell by cell, because the reference implementation judges the declaration ahead of its allow-rule lookup and its bypass release.
|
|
469
|
+
* `cross_boundary` is an ask carrying `crossBoundaryMandated` (the gate's cross-boundary station mints it with the real-approval bit; the
|
|
470
|
+
* decision receipt adds the bit to a policy's or hook's own): no rule, classifier, sandbox or mode answers it.
|
|
468
471
|
* `unstated` is the mode's answer for a call nobody stated: a tool whose permission seat is `passthrough` (it states no
|
|
469
|
-
* permission of its own — a remote peer's tool) reached the end of the fold with an allow no
|
|
472
|
+
* permission of its own — a remote peer's tool) reached the end of the fold with an allow no answer covers.
|
|
470
473
|
* `classifier_no_ruling` is a classifier station's question to a person because the auto-mode classification did not rule
|
|
471
474
|
* (`classifierNoRuling` — it could not run, or replied with no verdict): the denial-limit fallback's row (only a person
|
|
472
475
|
* answers it; the classifier is never asked again).
|
|
@@ -480,7 +483,7 @@ export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
|
|
|
480
483
|
* `rule_unreadable` is a rule that may govern the call and cannot be read for it: the organization's snapshot, a partition of
|
|
481
484
|
* the person's store, or this call against a deny / ask row (the lexer's word, a content row, the read station's rows).
|
|
482
485
|
*/
|
|
483
|
-
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "target_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
486
|
+
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "cross_boundary", "operator_forced", "shell_floor", "target_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
|
|
484
487
|
export type AskClass = (typeof ASK_CLASSES)[number];
|
|
485
488
|
/** Membership test for {@link AskClass} — the word crosses the wire on `tool_end.gate.disposition`. */
|
|
486
489
|
export declare function isAskClass(v: unknown): v is AskClass;
|
|
@@ -504,7 +507,8 @@ export interface ModeArbitrationRow {
|
|
|
504
507
|
* The mode × class table. `bypassImmune` is the one new column (nothing said it before); the classifier and
|
|
505
508
|
* sandbox columns are the fold of the two former per-word lane tables (only `probe` moved: the classifier may
|
|
506
509
|
* now answer it — the read boundary's unestablishable spelling is fed to it as a deny-table row instead), and
|
|
507
|
-
* `ruleMayClear`
|
|
510
|
+
* `ruleMayClear` is the allow layer's ONE reading: an allow member clears a standing ask exactly when the folded row says so
|
|
511
|
+
* (every class a real-approval bit, a governance verdict, a hook, a mark or an ask rule brings says no).
|
|
508
512
|
* `probe`'s `classifierEligible` is the OPEN read face's reading: a narrowed (`roots`) face withholds the classifier from the class,
|
|
509
513
|
* folded where the ask's row is ({@link foldArbitrationRow}).
|
|
510
514
|
* `mode` is the permission mode's OWN question (`plan`'s two sentences, `decisionReason: "mode"`): the reference
|
|
@@ -610,6 +614,13 @@ export declare const MODE_ARBITRATION: {
|
|
|
610
614
|
readonly ruleMayClear: false;
|
|
611
615
|
readonly hookAskFloor: true;
|
|
612
616
|
};
|
|
617
|
+
readonly cross_boundary: {
|
|
618
|
+
readonly bypassImmune: true;
|
|
619
|
+
readonly classifierEligible: false;
|
|
620
|
+
readonly sandboxAdmission: false;
|
|
621
|
+
readonly ruleMayClear: false;
|
|
622
|
+
readonly hookAskFloor: false;
|
|
623
|
+
};
|
|
613
624
|
readonly operator_forced: {
|
|
614
625
|
readonly bypassImmune: true;
|
|
615
626
|
readonly classifierEligible: true;
|
|
@@ -751,6 +762,9 @@ export interface AskClassification {
|
|
|
751
762
|
readonly classes: ReadonlySet<AskClass>;
|
|
752
763
|
readonly askClass: AskClass;
|
|
753
764
|
readonly row: ModeArbitrationRow;
|
|
765
|
+
/** The tool face the classification was derived on ({@link AskClassFacts.askFace}); absent ⇒ the face-less reading, the `other`
|
|
766
|
+
* face's. The mode point and the headless remedy read the face here and nowhere else ({@link bypassImmuneOf}). */
|
|
767
|
+
readonly askFace?: AskFace;
|
|
754
768
|
}
|
|
755
769
|
/** The two members an ask's classification puts on the approval request and the park row: the disclosed word and
|
|
756
770
|
* the whole set, the set as an array in {@link ASK_CLASSES} order (so `classes[0] === askClass`). */
|
|
@@ -859,14 +873,16 @@ export type Arbitration = {
|
|
|
859
873
|
* The ONE mode read point. `dontAsk` refuses every ask — one value for every class — but a hook's that no member of the
|
|
860
874
|
* set floors (`hookAskFloor`: a hook's ask beside a rule's or a safety check's ask is refused, as the reference's full
|
|
861
875
|
* pipeline refuses it; a hook's ask alone is the reference's forced decision, which never reaches that conversion);
|
|
862
|
-
* `bypassPermissions` refuses a write whose target's reading could not say where it leads where
|
|
863
|
-
* (every face but the shell's: a person cannot be asked — the
|
|
864
|
-
* immune class
|
|
865
|
-
*
|
|
876
|
+
* `bypassPermissions` refuses a write whose target's reading could not say where it leads on a face where THAT class stays
|
|
877
|
+
* immune (every face but the shell's: a person cannot be asked — the class's own reading on the face the classification records,
|
|
878
|
+
* {@link bypassImmuneOn}; another immune class beside it does not make it immune), keeps every immune class and releases the
|
|
879
|
+
* rest: the shell face releases that class ({@link RELEASED_ON_SHELL_FACE}), the reference implementation's answer for a
|
|
880
|
+
* command whose written target could not be resolved — so on that face an immune class beside it keeps the question;
|
|
866
881
|
* `acceptEdits` answers an ORDINARY ask itself on a call the caller vouches is one of the mode's file commands
|
|
867
882
|
* (`call.fileCommand`, `accepted`); `plan`, `default` and `auto` hand every ask on (plan's own questions were raised by
|
|
868
883
|
* its write gate; the reference implementation refuses nothing at this point under plan — auto's classifier reads the
|
|
869
|
-
* row at its own station). Reads the class SET
|
|
884
|
+
* row at its own station). Reads the class SET on the face the classification records ({@link bypassImmuneOf}) and the folded
|
|
885
|
+
* row's face-free `hookAskFloor` — never the row's `bypassImmune`, never the disclosed word.
|
|
870
886
|
*/
|
|
871
887
|
export declare function arbitrateByMode(mode: PermissionMode, c: AskClassification, disclosure?: {
|
|
872
888
|
origin?: AskOrigin;
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -25,7 +25,7 @@ export function stampAskOrigin(decision, facts, classification) {
|
|
|
25
25
|
}
|
|
26
26
|
export const RULE_OFFERS_ABSENCES = Object.freeze(["mandated", "allow_outranked", "ask_rule_matched", "lane_cannot_speak"]);
|
|
27
27
|
export function ruleClearanceOf(ask, marks) {
|
|
28
|
-
const marksWord = persistedRuleMandateOf({ ...marks, probeMandated: ask?.probeMandated === true });
|
|
28
|
+
const marksWord = ask?.crossBoundaryMandated === true ? "cross_boundary_taint" : persistedRuleMandateOf({ ...marks, probeMandated: ask?.probeMandated === true });
|
|
29
29
|
const foldedDoor = (ask?.foldedAskClasses ?? []).some((c) => !MODE_ARBITRATION[c].ruleMayClear);
|
|
30
30
|
const doorOutsideTable = ask?.requiresRealApproval === true || ask?.decisionReason === "hook" || ask?.decisionReason === "mode" || foldedDoor || ask?.authoredBy === "deployment" || ask?.inheritedUnresolved === true || ask?.ancestorResolved === true;
|
|
31
31
|
const mandated = ask?.mandated === true || doorOutsideTable || marksWord !== undefined;
|
|
@@ -50,7 +50,7 @@ export function ruleClearanceOf(ask, marks) {
|
|
|
50
50
|
return "mandated";
|
|
51
51
|
return mandated ? "mandated" : undefined;
|
|
52
52
|
})();
|
|
53
|
-
return { ...(word !== undefined ? { word } : {}), mandated,
|
|
53
|
+
return { ...(word !== undefined ? { word } : {}), mandated, ...(mandate !== undefined ? { mandate } : {}) };
|
|
54
54
|
}
|
|
55
55
|
export function askMandateMember(ask) {
|
|
56
56
|
const { mandated, mandate } = ruleClearanceOf(ask, {});
|
|
@@ -58,8 +58,9 @@ export function askMandateMember(ask) {
|
|
|
58
58
|
return mandated ? { mandated: true, ...(mandate !== undefined ? { mandate } : {}), ...authored } : authored;
|
|
59
59
|
}
|
|
60
60
|
export function ownedClearanceFacts(d) {
|
|
61
|
-
const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated, authoredBy, foldedAskClasses } = d;
|
|
61
|
+
const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated, crossBoundaryMandated, authoredBy, foldedAskClasses } = d;
|
|
62
62
|
return {
|
|
63
|
+
...(crossBoundaryMandated === true ? { crossBoundaryMandated } : {}),
|
|
63
64
|
...(mandated === true ? { mandated } : {}),
|
|
64
65
|
...(Array.isArray(foldedAskClasses) && foldedAskClasses.length > 0 ? { foldedAskClasses: [...foldedAskClasses] } : {}),
|
|
65
66
|
...(authoredBy === "deployment" ? { authoredBy } : {}),
|
|
@@ -106,7 +107,7 @@ export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
|
106
107
|
safety_tighten: false,
|
|
107
108
|
policy: false,
|
|
108
109
|
};
|
|
109
|
-
export const PERSISTED_RULE_MANDATES = Object.freeze(["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "target_unresolved"]);
|
|
110
|
+
export const PERSISTED_RULE_MANDATES = Object.freeze(["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "target_unresolved", "cross_boundary_taint"]);
|
|
110
111
|
const PERSISTED_RULE_MANDATE_SET = new Set(PERSISTED_RULE_MANDATES);
|
|
111
112
|
export function isPersistedRuleMandate(v) {
|
|
112
113
|
return PERSISTED_RULE_MANDATE_SET.has(v);
|
|
@@ -118,6 +119,7 @@ const MANDATE_DISCLOSURE = {
|
|
|
118
119
|
tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)",
|
|
119
120
|
write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule",
|
|
120
121
|
target_unresolved: "where this write leads on disk could not be determined (a link on the way could not be examined, or the environment could not resolve the target), so whether it lands on a guarded path is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can",
|
|
122
|
+
cross_boundary_taint: "this run executes on a device outside the deployment's trust boundary and the deployment is multi-tenant, so an org-side write — or, after an off-target read, a call bound for the device — is confirmed by the deployment's approver for this call: no standing allow rule and no automatic lane answers for it, and an allow the configuration gives (a blanket seat or a policy's answer) is refused",
|
|
121
123
|
};
|
|
122
124
|
export function mandateDisclosureOf(mandate) {
|
|
123
125
|
return mandate === undefined ? "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
|
|
@@ -183,6 +185,7 @@ export const ASK_CLASSES = Object.freeze([
|
|
|
183
185
|
"denial_limit_fallback",
|
|
184
186
|
"classifier_no_ruling",
|
|
185
187
|
"requires_user_interaction",
|
|
188
|
+
"cross_boundary",
|
|
186
189
|
"operator_forced",
|
|
187
190
|
"shell_floor",
|
|
188
191
|
"target_unresolved",
|
|
@@ -215,6 +218,7 @@ export const MODE_ARBITRATION = {
|
|
|
215
218
|
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
216
219
|
classifier_no_ruling: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
217
220
|
requires_user_interaction: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
221
|
+
cross_boundary: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
218
222
|
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
219
223
|
shell_floor: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
220
224
|
target_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
@@ -236,8 +240,11 @@ const RELEASED_ON_SHELL_FACE = new Set(["target_unresolved"]);
|
|
|
236
240
|
function bypassImmuneOn(c, face) {
|
|
237
241
|
return MODE_ARBITRATION[c].bypassImmune && !(face === "shell" && RELEASED_ON_SHELL_FACE.has(c));
|
|
238
242
|
}
|
|
243
|
+
function bypassImmuneOf(c) {
|
|
244
|
+
return [...c.classes].some((k) => bypassImmuneOn(k, c.askFace));
|
|
245
|
+
}
|
|
239
246
|
const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]);
|
|
240
|
-
export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
247
|
+
export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction", "cross_boundary"]);
|
|
241
248
|
const RELEASABLE_BY_FACE = Object.freeze({
|
|
242
249
|
shell: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "shell"))),
|
|
243
250
|
other: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "other"))),
|
|
@@ -280,6 +287,8 @@ export function decisionAskClassesOf(d) {
|
|
|
280
287
|
out.add("operator_forced");
|
|
281
288
|
if (d.probeMandated === true)
|
|
282
289
|
out.add("probe");
|
|
290
|
+
if (d.crossBoundaryMandated === true)
|
|
291
|
+
out.add("cross_boundary");
|
|
283
292
|
if (d.decisionReason === "hook")
|
|
284
293
|
out.add("hook");
|
|
285
294
|
if (d.decisionReason === "mode")
|
|
@@ -323,7 +332,7 @@ export function classifyAsk(d, facts) {
|
|
|
323
332
|
classes.add("requires_user_interaction");
|
|
324
333
|
if (marks.shellGated === true && marks.irreversibility === "always")
|
|
325
334
|
classes.add("shell_floor");
|
|
326
|
-
const toolMarked = marks.
|
|
335
|
+
const toolMarked = marks.egress === true || (marks.shellGated !== true && (marks.irreversibility === "always" || marks.irreversibility === "maybe"));
|
|
327
336
|
if (toolMarked) {
|
|
328
337
|
const sources = facts.toolMarkSources;
|
|
329
338
|
if (sources === undefined || sources.size === 0 || sources.has("deployment"))
|
|
@@ -352,7 +361,7 @@ export function classifyAsk(d, facts) {
|
|
|
352
361
|
if (classes.size === 0)
|
|
353
362
|
classes.add("ordinary");
|
|
354
363
|
const askClass = ASK_CLASSES.find((c) => classes.has(c));
|
|
355
|
-
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace, facts.askFace) };
|
|
364
|
+
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace, facts.askFace), ...(facts.askFace !== undefined ? { askFace: facts.askFace } : {}) };
|
|
356
365
|
}
|
|
357
366
|
export function headlessDenialMessage(toolName, askMessage) {
|
|
358
367
|
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|
|
@@ -366,12 +375,12 @@ export function headlessApprovalRemedy(input) {
|
|
|
366
375
|
const c = input.classification;
|
|
367
376
|
if (input.mode === "acceptEdits" && input.unplacedOptions !== undefined && (c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"))))
|
|
368
377
|
return `${fileCommandOptionsSentence(input.unplacedOptions.program)} Spell the ${input.unplacedOptions.program} without option words for the mode to answer it, or ${approver}`;
|
|
369
|
-
if (c
|
|
378
|
+
if (c !== undefined && bypassImmuneOf(c))
|
|
370
379
|
return `no permission mode releases this ask (${c.askClass}): ${approver}, or change the call`;
|
|
371
380
|
if (input.mode === "plan" && c?.classes.has("mode") === true)
|
|
372
381
|
return `the plan permission mode asks before every write and every call to a tool the engine does not define that is not read-only — leave plan mode (\`setPermissionMode\` to another mode, or approve the plan) to run it, or ${approver}`;
|
|
373
382
|
if (c !== undefined && c.classes.size === 1 && c.classes.has("unstated"))
|
|
374
|
-
return `no
|
|
383
|
+
return `no allow entry, stored allow rule or hook granted this tool, and it states no permission of its own — grant it with an allow entry for its name (\`RunnerDeps.allowedTools\` / \`TaskSpec.allowedTools\`), run with \`permissionMode: "bypassPermissions"\`, or ${approver}`;
|
|
375
384
|
const onlyOrdinary = c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"));
|
|
376
385
|
if (input.coveredWrite && onlyOrdinary) {
|
|
377
386
|
if (input.mode === "acceptEdits")
|
|
@@ -391,9 +400,9 @@ export function arbitrateByMode(mode, c, disclosure, call) {
|
|
|
391
400
|
case "dontAsk":
|
|
392
401
|
return has("hook") && !c.row.hookAskFloor ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
393
402
|
case "bypassPermissions":
|
|
394
|
-
if (has("target_unresolved") && c.
|
|
403
|
+
if (has("target_unresolved") && bypassImmuneOn("target_unresolved", c.askFace))
|
|
395
404
|
return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
396
|
-
if (c
|
|
405
|
+
if (bypassImmuneOf(c))
|
|
397
406
|
return { kind: "ask" };
|
|
398
407
|
const reason = [...MODE_RELEASE_REASON_SET].find((r) => has(r));
|
|
399
408
|
return {
|
|
@@ -154,7 +154,6 @@ export type QuestionOutcomeReading =
|
|
|
154
154
|
* meant to withhold. Neither guess is safe, so neither is made.
|
|
155
155
|
*/
|
|
156
156
|
export declare function classifyQuestionOutcome(outcome: OnQuestionOutcome): QuestionOutcomeReading;
|
|
157
|
-
export declare function canonicalizeCapturedPlainData(node: unknown, seen?: WeakSet<object>): boolean;
|
|
158
157
|
/**
|
|
159
158
|
* Narrow an {@link OnQuestionOutcome} to its no-interaction arm — the ONLY value that licenses
|
|
160
159
|
* re-routing a question to a later decider, so it is read strictly (the discriminant must be present
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { uuidv7 } from "../engine/session/uuid.js";
|
|
3
|
-
import { boundInputHashOf } from "./canonical-json.js";
|
|
3
|
+
import { boundInputHashOf, capturedJsonOf } from "./canonical-json.js";
|
|
4
4
|
import { defineTool, errorResult } from "./tools.js";
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { toolFace } from "./tool-catalog-entries.js";
|
|
@@ -25,73 +25,10 @@ export function classifyQuestionOutcome(outcome) {
|
|
|
25
25
|
return { shape: "unavailable" };
|
|
26
26
|
if (!carriesAnswers)
|
|
27
27
|
return { shape: "contradictory" };
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
captured = structuredClone(answers);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
28
|
+
const captured = capturedJsonOf(answers, "list");
|
|
29
|
+
if (!captured.ok)
|
|
33
30
|
return { shape: "contradictory" };
|
|
34
|
-
}
|
|
35
|
-
if (!isCanonicalAnswerTree(captured))
|
|
36
|
-
return { shape: "contradictory" };
|
|
37
|
-
return { shape: "answered", answer: { answers: captured } };
|
|
38
|
-
}
|
|
39
|
-
function isCanonicalAnswerTree(answers) {
|
|
40
|
-
return canonicalizeCapturedPlainData(answers);
|
|
41
|
-
}
|
|
42
|
-
function isUnknownArray(v) {
|
|
43
|
-
return Array.isArray(v);
|
|
44
|
-
}
|
|
45
|
-
export function canonicalizeCapturedPlainData(node, seen = new WeakSet()) {
|
|
46
|
-
if (node === null)
|
|
47
|
-
return true;
|
|
48
|
-
switch (typeof node) {
|
|
49
|
-
case "string":
|
|
50
|
-
case "boolean":
|
|
51
|
-
return true;
|
|
52
|
-
case "number":
|
|
53
|
-
return Number.isFinite(node);
|
|
54
|
-
case "object":
|
|
55
|
-
break;
|
|
56
|
-
default:
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
const obj = node;
|
|
60
|
-
if (seen.has(obj))
|
|
61
|
-
return false;
|
|
62
|
-
seen.add(obj);
|
|
63
|
-
if (isUnknownArray(obj)) {
|
|
64
|
-
const arr = obj;
|
|
65
|
-
for (let i = 0; i < arr.length; i++) {
|
|
66
|
-
if (!Object.prototype.hasOwnProperty.call(arr, i))
|
|
67
|
-
return false;
|
|
68
|
-
if (arr[i] === undefined)
|
|
69
|
-
return false;
|
|
70
|
-
if (Object.is(arr[i], -0))
|
|
71
|
-
arr[i] = 0;
|
|
72
|
-
if (!canonicalizeCapturedPlainData(arr[i], seen))
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
if (Object.keys(arr).length !== arr.length)
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
const proto = Object.getPrototypeOf(obj);
|
|
80
|
-
if (proto !== Object.prototype && proto !== null)
|
|
81
|
-
return false;
|
|
82
|
-
const rec = obj;
|
|
83
|
-
for (const key of Object.keys(rec)) {
|
|
84
|
-
if (rec[key] === undefined) {
|
|
85
|
-
delete rec[key];
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (Object.is(rec[key], -0))
|
|
89
|
-
rec[key] = 0;
|
|
90
|
-
if (!canonicalizeCapturedPlainData(rec[key], seen))
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return true;
|
|
31
|
+
return { shape: "answered", answer: { answers: captured.value } };
|
|
95
32
|
}
|
|
96
33
|
export function isQuestionUnavailable(outcome) {
|
|
97
34
|
return classifyQuestionOutcome(outcome).shape === "unavailable";
|
|
@@ -48,8 +48,9 @@ export interface AskUnresolvableFacts {
|
|
|
48
48
|
* did not take the call — it declined or failed; `false`: no park lane at all, the usual shape). */
|
|
49
49
|
readonly parkLaneExisted: boolean;
|
|
50
50
|
/** Present for the causes that say nobody could be asked ({@link askUnresolvableRemedySeat}): the caller-side change that
|
|
51
|
-
* would have let the call run, as the gate minted it for the refusal's tool text (`headlessApprovalRemedy`, ask-origin.ts
|
|
52
|
-
*
|
|
51
|
+
* would have let the call run, as the gate minted it for the refusal's tool text (`headlessApprovalRemedy`, ask-origin.ts;
|
|
52
|
+
* for arguments no approval can be bound to, the binding's own sentence, which is that text — the remedy's approver half would
|
|
53
|
+
* not let them run) — carried verbatim, never re-composed here. */
|
|
53
54
|
readonly remedy?: string;
|
|
54
55
|
}
|
|
55
56
|
/**
|