@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.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/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
- package/dist/__test-utils__/approval-contract/types.js +24 -0
- package/dist/__test-utils__/approval-contract/types.js.map +1 -0
- package/dist/activities/call-agent-status.d.ts +19 -1
- package/dist/activities/call-agent-status.js +64 -5
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-llm.js +19 -53
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +24 -0
- package/dist/activities/classify-tool-approvals.js +69 -17
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +11 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
- package/dist/activities/execute-cursor/approval-policy.js +8 -120
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
- package/dist/activities/execute-cursor/approval-state.js +233 -17
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
- package/dist/activities/execute-cursor/capture-flow.js +234 -0
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
- package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
- package/dist/activities/execute-cursor/cas-observations.js +184 -0
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
- package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
- package/dist/activities/execute-cursor/command-provenance.js +128 -0
- package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
- package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
- package/dist/activities/execute-cursor/exact-apply.js +204 -0
- package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
- package/dist/activities/execute-cursor/hook-script.js +310 -47
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +8 -1
- package/dist/activities/execute-cursor/index.js +500 -64
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
- package/dist/activities/execute-cursor/message-translator.js +836 -90
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
- package/dist/activities/execute-cursor/prompt-builder.js +74 -15
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
- package/dist/activities/execute-cursor/workspace-setup.js +212 -33
- package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
- package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
- package/dist/activities/execute-deep-agent/hitl.js +5 -1
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.d.ts +2 -1
- package/dist/activities/execute-deep-agent/index.js +370 -56
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
- package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
- package/dist/activities/execute-deep-agent/setup.js +149 -92
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
- package/dist/activities/execute-deep-agent/status-builder.js +6 -2
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming.js +3 -15
- package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +9 -5
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +2 -0
- package/dist/client/stigmer-client.js +2 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +85 -4
- package/dist/middleware/approval-gate.js +165 -38
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/types.d.ts +2 -5
- package/dist/shared/activity-input.d.ts +43 -0
- package/dist/shared/activity-input.js +17 -0
- package/dist/shared/activity-input.js.map +1 -0
- package/dist/shared/approval-canonicalize.d.ts +19 -0
- package/dist/shared/approval-canonicalize.js +119 -0
- package/dist/shared/approval-canonicalize.js.map +1 -0
- package/dist/shared/approval-fingerprint.d.ts +106 -0
- package/dist/shared/approval-fingerprint.js +113 -0
- package/dist/shared/approval-fingerprint.js.map +1 -0
- package/dist/shared/approval-policy.d.ts +182 -12
- package/dist/shared/approval-policy.js +213 -27
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +52 -0
- package/dist/shared/args-preview.js +93 -0
- package/dist/shared/args-preview.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +19 -1
- package/dist/shared/artifact-storage.js +48 -11
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/file-change.d.ts +44 -0
- package/dist/shared/file-change.js +57 -0
- package/dist/shared/file-change.js.map +1 -0
- package/dist/shared/file-tools.d.ts +107 -0
- package/dist/shared/file-tools.js +168 -0
- package/dist/shared/file-tools.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +202 -0
- package/dist/shared/filereview/capture.js +498 -0
- package/dist/shared/filereview/capture.js.map +1 -0
- package/dist/shared/filereview/cas-substrate.d.ts +190 -0
- package/dist/shared/filereview/cas-substrate.js +284 -0
- package/dist/shared/filereview/cas-substrate.js.map +1 -0
- package/dist/shared/filereview/digest.d.ts +40 -0
- package/dist/shared/filereview/digest.js +66 -0
- package/dist/shared/filereview/digest.js.map +1 -0
- package/dist/shared/filereview/events.d.ts +170 -0
- package/dist/shared/filereview/events.js +298 -0
- package/dist/shared/filereview/events.js.map +1 -0
- package/dist/shared/filereview/git-substrate.d.ts +175 -0
- package/dist/shared/filereview/git-substrate.js +439 -0
- package/dist/shared/filereview/git-substrate.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +11 -0
- package/dist/shared/filereview/index.js +12 -0
- package/dist/shared/filereview/index.js.map +1 -0
- package/dist/shared/filereview/secret-paths.d.ts +63 -0
- package/dist/shared/filereview/secret-paths.js +105 -0
- package/dist/shared/filereview/secret-paths.js.map +1 -0
- package/dist/shared/fingerprint-secret.d.ts +26 -0
- package/dist/shared/fingerprint-secret.js +47 -0
- package/dist/shared/fingerprint-secret.js.map +1 -0
- package/dist/shared/model-client.d.ts +51 -0
- package/dist/shared/model-client.js +77 -0
- package/dist/shared/model-client.js.map +1 -0
- package/dist/shared/plan-artifact.js +0 -2
- package/dist/shared/plan-artifact.js.map +1 -1
- package/dist/shared/status-offload.d.ts +83 -9
- package/dist/shared/status-offload.js +399 -79
- package/dist/shared/status-offload.js.map +1 -1
- package/dist/shared/status.js +14 -1
- package/dist/shared/status.js.map +1 -1
- package/dist/shared/tool-kind.d.ts +19 -0
- package/dist/shared/tool-kind.js +13 -0
- package/dist/shared/tool-kind.js.map +1 -1
- package/dist/shared/tool-row.d.ts +88 -0
- package/dist/shared/tool-row.js +127 -0
- package/dist/shared/tool-row.js.map +1 -0
- package/dist/shared/workspace/platform-dir.d.ts +25 -0
- package/dist/shared/workspace/platform-dir.js +38 -2
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.js +56 -7
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.d.ts +50 -0
- package/dist/workflows/connect-mcp-server.js +136 -15
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +2 -2
- package/src/__test-utils__/approval-contract/contract.ts +224 -0
- package/src/__test-utils__/approval-contract/types.ts +179 -0
- package/src/__test-utils__/fake-artifact-storage.ts +72 -0
- package/src/__tests__/approval-gateway-contract.test.ts +29 -0
- package/src/__tests__/claimcheck-codec.test.ts +16 -53
- package/src/__tests__/golden-e2e.test.ts +2 -0
- package/src/__tests__/runner-token-coordinator.test.ts +3 -3
- package/src/activities/__tests__/call-agent-status.test.ts +135 -0
- package/src/activities/__tests__/call-llm.test.ts +1 -1
- package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
- package/src/activities/call-agent-status.ts +74 -4
- package/src/activities/call-llm.ts +18 -63
- package/src/activities/classify-tool-approvals.ts +101 -19
- package/src/activities/discover-mcp-server.ts +29 -1
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
- package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
- package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
- package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
- package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
- package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
- package/src/activities/execute-cursor/approval-policy.ts +28 -159
- package/src/activities/execute-cursor/approval-state.ts +366 -18
- package/src/activities/execute-cursor/capture-flow.ts +323 -0
- package/src/activities/execute-cursor/cas-observations.ts +204 -0
- package/src/activities/execute-cursor/command-provenance.ts +168 -0
- package/src/activities/execute-cursor/exact-apply.ts +253 -0
- package/src/activities/execute-cursor/hook-script.ts +317 -51
- package/src/activities/execute-cursor/index.ts +575 -67
- package/src/activities/execute-cursor/message-translator.ts +963 -89
- package/src/activities/execute-cursor/prompt-builder.ts +80 -14
- package/src/activities/execute-cursor/workspace-setup.ts +257 -42
- package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
- package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
- package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
- package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
- package/src/activities/execute-deep-agent/hitl.ts +15 -1
- package/src/activities/execute-deep-agent/index.ts +434 -64
- package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
- package/src/activities/execute-deep-agent/setup.ts +223 -125
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
- package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
- package/src/activities/execute-deep-agent/status-builder.ts +19 -7
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
- package/src/activities/execute-deep-agent/streaming.ts +3 -13
- package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
- package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
- package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
- package/src/claimcheck/payload-codec.ts +8 -8
- package/src/client/stigmer-client.ts +9 -1
- package/src/middleware/__tests__/approval-gate.test.ts +488 -4
- package/src/middleware/approval-gate.ts +247 -38
- package/src/middleware/types.ts +5 -5
- package/src/shared/__tests__/activity-input.test.ts +78 -0
- package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
- package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
- package/src/shared/__tests__/approval-policy.test.ts +274 -40
- package/src/shared/__tests__/args-preview.test.ts +78 -0
- package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
- package/src/shared/__tests__/artifact-storage.test.ts +123 -11
- package/src/shared/__tests__/file-change.test.ts +85 -0
- package/src/shared/__tests__/file-tools.test.ts +90 -0
- package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
- package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
- package/src/shared/__tests__/model-client.test.ts +162 -0
- package/src/shared/__tests__/plan-artifact.test.ts +11 -26
- package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
- package/src/shared/__tests__/status-offload.test.ts +573 -16
- package/src/shared/__tests__/status.test.ts +4 -5
- package/src/shared/__tests__/tool-kind.test.ts +24 -1
- package/src/shared/__tests__/tool-row.test.ts +221 -0
- package/src/shared/activity-input.ts +57 -0
- package/src/shared/approval-canonicalize.ts +159 -0
- package/src/shared/approval-fingerprint.ts +148 -0
- package/src/shared/approval-policy.ts +303 -27
- package/src/shared/args-preview.ts +98 -0
- package/src/shared/artifact-storage.ts +62 -11
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
- package/src/shared/file-change.ts +64 -0
- package/src/shared/file-tools.ts +169 -0
- package/src/shared/filereview/__tests__/capture.test.ts +856 -0
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
- package/src/shared/filereview/__tests__/digest.test.ts +100 -0
- package/src/shared/filereview/__tests__/events.test.ts +245 -0
- package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
- package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
- package/src/shared/filereview/capture.ts +727 -0
- package/src/shared/filereview/cas-substrate.ts +401 -0
- package/src/shared/filereview/digest.ts +83 -0
- package/src/shared/filereview/events.ts +449 -0
- package/src/shared/filereview/git-substrate.ts +555 -0
- package/src/shared/filereview/index.ts +60 -0
- package/src/shared/filereview/secret-paths.ts +121 -0
- package/src/shared/fingerprint-secret.ts +53 -0
- package/src/shared/model-client.ts +122 -0
- package/src/shared/plan-artifact.ts +0 -2
- package/src/shared/status-offload.ts +433 -77
- package/src/shared/status.ts +13 -0
- package/src/shared/tool-kind.ts +33 -0
- package/src/shared/tool-row.ts +135 -0
- package/src/shared/workspace/platform-dir.ts +41 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
- package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
- package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
- package/src/workflows/call-agent-orchestrator.ts +53 -6
- package/src/workflows/connect-mcp-server.ts +179 -24
- package/src/workflows/types.ts +8 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval fingerprint — the exact-match enforcement identity for the HITL Tool
|
|
3
|
+
* Execution Gateway (Phase 2).
|
|
4
|
+
*
|
|
5
|
+
* A fingerprint is `HMAC-SHA256(key, canonicalForm)` over a canonicalized tool
|
|
6
|
+
* action (see {@link file://./approval-canonicalize.ts}). It answers exactly one
|
|
7
|
+
* question at the moment of side effect: "is *this* action the one the user
|
|
8
|
+
* approved?" An approval is bound to the canonical identity of the action, not to
|
|
9
|
+
* the transient id of the proposing tool call, so a re-driven action that differs
|
|
10
|
+
* from what was approved is re-asked — never silently executed.
|
|
11
|
+
*
|
|
12
|
+
* Why HMAC and not a bare SHA-256 (binding canonical decision,
|
|
13
|
+
* design-decisions/approval-fingerprint-vs-march-rollback.md, Rule 4): the
|
|
14
|
+
* fingerprint is an authorization token, not a correlation key. Keying it under a
|
|
15
|
+
* Stigmer-held secret means a model (or a compromised workspace) cannot forge a
|
|
16
|
+
* value that the gateway will accept. In Phase 2 the fingerprint is
|
|
17
|
+
* recompute-and-compare at one trusted layer (no bearer token crosses a wire), so
|
|
18
|
+
* the anti-forgery property is forward-looking — it earns its keep when a lease
|
|
19
|
+
* later becomes a server-issued bearer token (Phase 7). We pay the small cost now
|
|
20
|
+
* to avoid a migration that re-keys every persisted approval later.
|
|
21
|
+
*
|
|
22
|
+
* Two fidelities share this one canonicalization core, because the two
|
|
23
|
+
* enforcement substrates differ (see the Phase-2 plan, "two enforcement
|
|
24
|
+
* substrates, not one"):
|
|
25
|
+
*
|
|
26
|
+
* - FULL ({@link computeApprovalFingerprint}) — HMAC over the full canonical
|
|
27
|
+
* action (tool + paths + shellCommand + args). Used by the in-process
|
|
28
|
+
* deep-agent gateway, where approve-time and execute-time observe the same
|
|
29
|
+
* arg shape (LangGraph checkpoint replay), so the action can be matched at
|
|
30
|
+
* full fidelity.
|
|
31
|
+
* - COARSE ({@link computeCoarseApprovalFingerprint}) — HMAC over
|
|
32
|
+
* (category, salient) only. Used by the out-of-process Cursor deny-oracle
|
|
33
|
+
* hook, whose stdin payload names the same action with a different taxonomy
|
|
34
|
+
* (`Write` vs `edit`, `file_path` vs `path`) and cannot reproduce the full
|
|
35
|
+
* args. The coarse projection is the documented, substrate-forced coarsening
|
|
36
|
+
* that lets the hook-side and stream-side fingerprints agree; it is the
|
|
37
|
+
* successor identity for execute-cursor's grant token.
|
|
38
|
+
*
|
|
39
|
+
* The fingerprint is enforcement-only and never a correlation key — correlation
|
|
40
|
+
* stays `approval_request_id` + `tool_call_id`.
|
|
41
|
+
*/
|
|
42
|
+
import { createHmac } from "node:crypto";
|
|
43
|
+
import { canonicalJson, canonicalToolActionJson, canonicalizeToolAction, } from "./approval-canonicalize.js";
|
|
44
|
+
import { toolApprovalCategory } from "./tool-kind.js";
|
|
45
|
+
/**
|
|
46
|
+
* Version tag prefixed to every fingerprint. Bumping it is the migration lever
|
|
47
|
+
* if the canonical form or the MAC primitive ever changes: an old lease and a
|
|
48
|
+
* new computation will not compare equal, so a version skew re-asks (safe) rather
|
|
49
|
+
* than silently mismatching. Keep in lockstep with the Go/Java editions.
|
|
50
|
+
*/
|
|
51
|
+
export const APPROVAL_FINGERPRINT_VERSION = "v1";
|
|
52
|
+
/**
|
|
53
|
+
* Full-fidelity fingerprint for the in-process deep-agent gateway. Distinct
|
|
54
|
+
* actions (different tool, paths, command, or args) yield distinct fingerprints;
|
|
55
|
+
* the same action is byte-stable across re-invocations.
|
|
56
|
+
*/
|
|
57
|
+
export function computeApprovalFingerprint(key, input) {
|
|
58
|
+
return tagged(hmacHex(key, canonicalToolActionJson(input)));
|
|
59
|
+
}
|
|
60
|
+
export function coarseToolIdentity(input) {
|
|
61
|
+
const canonical = canonicalizeToolAction(input);
|
|
62
|
+
if (canonical.mcpServerSlug) {
|
|
63
|
+
return { tool: input.toolName.trim(), mcpServerSlug: canonical.mcpServerSlug, salient: "" };
|
|
64
|
+
}
|
|
65
|
+
const category = toolApprovalCategory(input.toolName);
|
|
66
|
+
return {
|
|
67
|
+
tool: category ?? input.toolName.trim(),
|
|
68
|
+
mcpServerSlug: "",
|
|
69
|
+
salient: canonical.paths[0] || canonical.shellCommand || "",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fingerprint an already-reduced {@link CoarseToolIdentity}. Split out from
|
|
74
|
+
* {@link computeCoarseApprovalFingerprint} so a substrate that has *already*
|
|
75
|
+
* reduced a tool call to its (tool, mcpServerSlug, salient) identity — the Cursor
|
|
76
|
+
* harness, whose hook and stream both key on a raw, un-normalized salient that
|
|
77
|
+
* the bash hook can reproduce without a workspace root — can fingerprint that
|
|
78
|
+
* exact identity through the one shared HMAC+canonical-JSON path, instead of
|
|
79
|
+
* re-deriving the salient via {@link canonicalizeToolAction} (which normalizes
|
|
80
|
+
* paths and would diverge from the hook's raw value).
|
|
81
|
+
*/
|
|
82
|
+
export function fingerprintCoarseIdentity(key, identity) {
|
|
83
|
+
return tagged(hmacHex(key, canonicalJson(identity)));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Coarse fingerprint for the out-of-process Cursor hook. By construction, two
|
|
87
|
+
* actions that name the same operation in different taxonomies (`Write` vs
|
|
88
|
+
* `edit`) over the same resource collapse to one fingerprint — this is what makes
|
|
89
|
+
* the hook-side (deny/grant) and stream-side (reconciliation) values agree.
|
|
90
|
+
*/
|
|
91
|
+
export function computeCoarseApprovalFingerprint(key, input) {
|
|
92
|
+
return fingerprintCoarseIdentity(key, coarseToolIdentity(input));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Derive the per-execution fingerprint key from a runner-held master secret.
|
|
96
|
+
*
|
|
97
|
+
* The key is scoped to one `execution_id`: stable across Temporal re-invocations
|
|
98
|
+
* of the same execution (the gateway approves on one invocation and enforces on
|
|
99
|
+
* the next), and isolated between executions so a fingerprint approved for one
|
|
100
|
+
* cannot be replayed against another. The master-secret source is wired when the
|
|
101
|
+
* gateway first consumes the fingerprint (Slices C/D); until then this is
|
|
102
|
+
* exercised only by tests against a fixed master secret.
|
|
103
|
+
*/
|
|
104
|
+
export function deriveExecutionFingerprintKey(masterSecret, executionId) {
|
|
105
|
+
return createHmac("sha256", masterSecret).update(executionId, "utf8").digest();
|
|
106
|
+
}
|
|
107
|
+
function hmacHex(key, canonical) {
|
|
108
|
+
return createHmac("sha256", key).update(canonical, "utf8").digest("hex");
|
|
109
|
+
}
|
|
110
|
+
function tagged(mac) {
|
|
111
|
+
return `${APPROVAL_FINGERPRINT_VERSION}:${mac}`;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=approval-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-fingerprint.js","sourceRoot":"","sources":["../../src/shared/approval-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,sBAAsB,GAEvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAKjD;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAmB,EAAE,KAAsB;IACpF,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAqBD,MAAM,UAAU,kBAAkB,CAAC,KAAsB;IACvD,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;IAChD,IAAI,SAAS,CAAC,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC9F,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO;QACL,IAAI,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE;QACvC,aAAa,EAAE,EAAE;QACjB,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,YAAY,IAAI,EAAE;KAC5D,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAmB,EAAE,QAA4B;IACzF,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gCAAgC,CAAC,GAAmB,EAAE,KAAsB;IAC1F,OAAO,yBAAyB,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,6BAA6B,CAAC,YAA4B,EAAE,WAAmB;IAC7F,OAAO,UAAU,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;AACjF,CAAC;AAED,SAAS,OAAO,CAAC,GAAmB,EAAE,SAAiB;IACrD,OAAO,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO,GAAG,4BAA4B,IAAI,GAAG,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -5,41 +5,211 @@
|
|
|
5
5
|
* 1. McpServerStatus.tool_approvals — system-generated defaults
|
|
6
6
|
* 2. McpServerSpec.pinned_tool_approvals — manual overrides
|
|
7
7
|
* 3. McpServerUsage.tool_approval_overrides — per-agent customization
|
|
8
|
-
* 4.
|
|
8
|
+
* 4. Active approval leases — the runtime bypass, now SCOPED: the pre-armed
|
|
9
|
+
* spec.auto_approve_all is a whole-run global bypass, while an interactive
|
|
10
|
+
* APPROVE_ALL ("approve all of this kind") grants a run-lifetime lease for
|
|
11
|
+
* only that action's scope (its built-in category, or its MCP server). See
|
|
12
|
+
* {@link ActiveLeases}.
|
|
9
13
|
*
|
|
10
14
|
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent
|
|
11
15
|
* (middleware interruptOn model) to determine which tools need approval.
|
|
12
16
|
*/
|
|
13
17
|
import type { ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
14
18
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
19
|
+
import { ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
20
|
+
import { type ToolApprovalCategory } from "./tool-kind.js";
|
|
15
21
|
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
16
22
|
/**
|
|
17
|
-
*
|
|
18
|
-
* carries an APPROVE_ALL decision.
|
|
23
|
+
* The set of run-lifetime approval leases active for an execution.
|
|
19
24
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* A lease is the scoped successor to the old all-or-nothing "approve all". When
|
|
26
|
+
* a user chooses APPROVE_ALL ("approve and don't ask again") at a gate it no
|
|
27
|
+
* longer disables the entire gate — it grants a lease for ONLY that action's
|
|
28
|
+
* scope, for the remainder of THIS execution: a mutating built-in category
|
|
29
|
+
* ({@link ToolApprovalCategory}) for a built-in tool, or an MCP server slug for
|
|
30
|
+
* an MCP tool. A different class of action proposed later is still gated.
|
|
31
|
+
*
|
|
32
|
+
* This is the DERIVED form of the lease — it is not (yet) a persisted proto.
|
|
33
|
+
* Each lease rides the `ToolCall.approval_action == APPROVE_ALL` decision that
|
|
34
|
+
* is already persisted and preserved (Go PreserveApprovalFields / Java
|
|
35
|
+
* ApprovalFieldPreserver), and its scope is recomputed on read from the tool's
|
|
36
|
+
* name + mcp_server_slug. Keeping it derived means one source of truth with
|
|
37
|
+
* nothing to drift; a persisted/transmitted `ApprovalLease` proto is warranted
|
|
38
|
+
* only once a lease must cross a trust boundary (a later phase).
|
|
39
|
+
*
|
|
40
|
+
* `global` is the one remaining UNSCOPED bypass: the deliberate, pre-armed
|
|
41
|
+
* spec.auto_approve_all ("trust this whole run", set before the run via
|
|
42
|
+
* CLI/API/CI). It is intentionally distinct from the interactive scoped leases.
|
|
43
|
+
*/
|
|
44
|
+
export interface ActiveLeases {
|
|
45
|
+
/** Pre-armed spec.auto_approve_all: the whole gate is inert for the run. */
|
|
46
|
+
readonly global: boolean;
|
|
47
|
+
/** Built-in approval categories with a run-lifetime lease. */
|
|
48
|
+
readonly categories: ReadonlySet<ToolApprovalCategory>;
|
|
49
|
+
/** MCP server slugs with a run-lifetime lease (covers all of the server's tools). */
|
|
50
|
+
readonly servers: ReadonlySet<string>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The class an APPROVE_ALL leases for a single tool call: an MCP tool leases its
|
|
54
|
+
* whole `server`, a gated built-in leases its `category`. `undefined` means the
|
|
55
|
+
* tool has no leasable scope (a read-only built-in, an unknown name).
|
|
56
|
+
*
|
|
57
|
+
* A discriminated union (not a `{ category?, server? }` bag) so callers cannot
|
|
58
|
+
* construct or observe the impossible "both set" / "neither set" states.
|
|
59
|
+
*/
|
|
60
|
+
export type LeaseScope = {
|
|
61
|
+
readonly kind: "category";
|
|
62
|
+
readonly category: ToolApprovalCategory;
|
|
63
|
+
} | {
|
|
64
|
+
readonly kind: "server";
|
|
65
|
+
readonly server: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Reduce a single tool call to the scope its APPROVE_ALL would lease — the core
|
|
69
|
+
* of {@link deriveActiveLeases}, extracted so the cross-edition lease-scope
|
|
70
|
+
* corpus (apis/testdata/hitl/lease-scope) can exercise it directly.
|
|
71
|
+
*
|
|
72
|
+
* The MCP server slug takes precedence over the built-in category and is used
|
|
73
|
+
* RAW (the server's identity, not case-folded), matching the Go
|
|
74
|
+
* {@link DeriveLeaseScope} and Java {@link LeaseScope.deriveKey} byte-for-byte.
|
|
75
|
+
* The category lookup reuses {@link toolApprovalCategory}, the shared oracle, so
|
|
76
|
+
* a built-in resolves to write/delete/shell (read-only built-ins are ungated and
|
|
77
|
+
* return `undefined`).
|
|
78
|
+
*/
|
|
79
|
+
export declare function deriveLeaseScope(toolName: string, mcpServerSlug: string): LeaseScope | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Derive the active approval leases for an execution.
|
|
82
|
+
*
|
|
83
|
+
* The scoped successor to the former all-or-nothing hasApproveAllDecision:
|
|
84
|
+
* instead of "any APPROVE_ALL anywhere disables the whole gate", each
|
|
85
|
+
* APPROVE_ALL decision is reduced (via {@link deriveLeaseScope}) to the SCOPE of
|
|
86
|
+
* the tool it was made on — the built-in category for a built-in tool (read-only
|
|
87
|
+
* tools are never gated, so a built-in lease is always write/delete/shell), or
|
|
88
|
+
* the MCP server slug for an MCP tool — and only that scope is auto-approved for
|
|
89
|
+
* the rest of the run.
|
|
90
|
+
*
|
|
91
|
+
* Scans root and sub-agent tool calls so a lease granted anywhere applies
|
|
92
|
+
* execution-wide (matching the prior cross-sub-agent behavior, now bounded by
|
|
93
|
+
* scope). Both harnesses call this so the contract is defined in exactly one
|
|
94
|
+
* place. The scope derivation reuses {@link toolApprovalCategory}, the same
|
|
95
|
+
* corpus-tested oracle the Go and Java editions mirror, so the backend's
|
|
96
|
+
* scope-aware bulk-approve and this runner-side evaluation can never disagree.
|
|
97
|
+
*/
|
|
98
|
+
export declare function deriveActiveLeases(execution: AgentExecution): ActiveLeases;
|
|
99
|
+
/**
|
|
100
|
+
* Provenance of a gate decision: which policy layer (or decision point) is
|
|
101
|
+
* responsible for the final requires-approval verdict.
|
|
102
|
+
*
|
|
103
|
+
* Mirrors the proto {@link ApprovalPolicySource} one for one (see
|
|
104
|
+
* {@link toProtoPolicySource}); persisted on `ToolCall.approval_policy_source`
|
|
105
|
+
* so every authorization is auditable, and still stamped on the shadow
|
|
106
|
+
* ExecutionReceipt as a defense-in-depth audit signal.
|
|
107
|
+
*
|
|
108
|
+
* `annotation_destructive_tighten` is first-class: the connect-time tightener
|
|
109
|
+
* (see applyDestructiveHintTightener) marks its force-gated entries with
|
|
110
|
+
* `ToolApprovalPolicy.from_destructive_hint`, which {@link mergeApprovalPolicies}
|
|
111
|
+
* reads to attribute the gate to the annotation rather than collapsing it into
|
|
112
|
+
* the classifier default.
|
|
113
|
+
*/
|
|
114
|
+
export type PolicySource = "classifier_default" | "pinned_override" | "agent_override" | "auto_approve_all" | "approval_lease" | "builtin_category" | "file_capture" | "annotation_destructive_tighten";
|
|
115
|
+
/**
|
|
116
|
+
* Monotonic identifier of the policy-engine logic that produced a decision,
|
|
117
|
+
* persisted on `ToolCall.policy_engine_version`. Bumped when the
|
|
118
|
+
* merge/classification semantics change so decisions made by different engine
|
|
119
|
+
* versions remain distinguishable in audits. Phase 7 made
|
|
120
|
+
* `annotation_destructive_tighten` a distinct, persisted source.
|
|
121
|
+
*/
|
|
122
|
+
export declare const POLICY_ENGINE_VERSION = "phase-7";
|
|
123
|
+
/**
|
|
124
|
+
* Map the runner-internal {@link PolicySource} to the persisted proto
|
|
125
|
+
* {@link ApprovalPolicySource}. `undefined` (a tool no policy layer governs —
|
|
126
|
+
* e.g. a read-only built-in) maps to UNSPECIFIED, so the persisted field is left
|
|
127
|
+
* at its default exactly as an unclassified `tool_kind` is. The 1:1 mapping keeps
|
|
128
|
+
* the runner's union and the proto enum from drifting (asserted by the
|
|
129
|
+
* cross-edition corpus).
|
|
130
|
+
*/
|
|
131
|
+
export declare function toProtoPolicySource(source: PolicySource | undefined): ApprovalPolicySource;
|
|
132
|
+
/**
|
|
133
|
+
* Derive the authorization provenance — which policy layer governs this tool —
|
|
134
|
+
* for persisting on `ToolCall.approval_policy_source`.
|
|
135
|
+
*
|
|
136
|
+
* This is the read-side twin of the gate's decision logic: same layered
|
|
137
|
+
* precedence, but it answers "which layer governs this call?" for EVERY tool
|
|
138
|
+
* (gated or auto-approved), so the StatusBuilders can stamp provenance on the
|
|
139
|
+
* tool call exactly where they stamp `tool_kind`. It returns `undefined` for a
|
|
140
|
+
* plain read-only built-in that no policy layer touches (the proto's
|
|
141
|
+
* APPROVAL_POLICY_SOURCE_UNSPECIFIED).
|
|
142
|
+
*
|
|
143
|
+
* Precedence:
|
|
144
|
+
* 1. Whole-run global bypass (pre-armed auto_approve_all) governs everything —
|
|
145
|
+
* it is *why* anything ran ungated, so it wins.
|
|
146
|
+
* 2. MCP tool: the merged policy carries the responsible layer when gated; an
|
|
147
|
+
* absent entry means the four-level chain cleared it (classifier base). A
|
|
148
|
+
* server-scoped lease also surfaces as an absent entry — distinguishing it
|
|
149
|
+
* would need the lease set threaded here and is deferred with the rest of the
|
|
150
|
+
* per-server lease provenance, so a lease-cleared MCP tool reads
|
|
151
|
+
* classifier_default (matching the gate).
|
|
152
|
+
* 3. Built-in: a mutating category is governed (leased → approval_lease, else
|
|
153
|
+
* builtin_category); a read-only built-in is governed by no layer → undefined.
|
|
154
|
+
*/
|
|
155
|
+
export declare function resolveApprovalProvenance(toolName: string, serverSlug: string, policies: ReadonlyMap<string, MergedToolPolicy>, leasedCategories: ReadonlySet<ToolApprovalCategory>, globalBypass: boolean): PolicySource | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* A single MCP tool's merged approval decision after evaluating all policy
|
|
158
|
+
* layers. This is the single, canonical shape shared by every harness; the
|
|
159
|
+
* Cursor harness re-exports it from here so the two harnesses can never drift.
|
|
25
160
|
*/
|
|
26
|
-
export declare function hasApproveAllDecision(execution: AgentExecution): boolean;
|
|
27
161
|
export interface MergedToolPolicy {
|
|
28
162
|
toolName: string;
|
|
29
163
|
mcpServerSlug: string;
|
|
30
164
|
requiresApproval: boolean;
|
|
31
165
|
approvalMessage: string;
|
|
166
|
+
/** Which policy layer set this verdict (provenance for the shadow receipt). */
|
|
167
|
+
source: PolicySource;
|
|
32
168
|
}
|
|
33
169
|
/**
|
|
34
170
|
* Merge approval policies from all four levels into a single lookup map.
|
|
35
171
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
172
|
+
* Each MCP server contributes its own set of policies, so the map is keyed by
|
|
173
|
+
* "serverSlug/toolName" to avoid collisions between servers.
|
|
174
|
+
*
|
|
175
|
+
* Policy chain (each level overrides the previous):
|
|
176
|
+
* 1. status.toolApprovals — system-generated defaults; presence = requires approval
|
|
177
|
+
* 2. spec.pinnedToolApprovals — manual overrides; presence = requires approval
|
|
178
|
+
* 3. agent tool_approval_overrides — explicit boolean per tool (enable OR disable)
|
|
179
|
+
* 4. active leases — runtime bypass (highest priority), now scoped
|
|
180
|
+
*
|
|
181
|
+
* The map carries ONLY the tools that require approval — a tool's absence means
|
|
182
|
+
* "auto-approved". Leases shape that absence:
|
|
183
|
+
* - On a global pre-arm ({@link ActiveLeases.global}) the map is empty.
|
|
184
|
+
* - A server-scoped lease drops that server's tools from the map entirely. This
|
|
185
|
+
* single omission makes EVERY substrate treat the server as auto-approved with
|
|
186
|
+
* no extra code — the deep-agent gate and StatusBuilder read the map, and the
|
|
187
|
+
* Cursor hook's mcpToolPolicies is built from it (the hook is not itself
|
|
188
|
+
* server-aware, so omission is the only way to lease an MCP server there).
|
|
189
|
+
* Built-in CATEGORY leases are NOT applied here — built-ins are not in this map;
|
|
190
|
+
* they are cleared at the gate (deep-agent) and the hook (Cursor) instead.
|
|
191
|
+
*
|
|
192
|
+
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent (middleware
|
|
193
|
+
* interruptOn model), so the four-level semantics are defined in exactly one
|
|
194
|
+
* place.
|
|
195
|
+
*/
|
|
196
|
+
export declare function mergeApprovalPolicies(resolvedServers: ResolvedMcpServer[], agentOverrides: ToolApprovalOverride[], leases: ActiveLeases): Map<string, MergedToolPolicy>;
|
|
197
|
+
/**
|
|
198
|
+
* Look up whether an MCP tool requires approval.
|
|
199
|
+
*
|
|
200
|
+
* @param toolName - The actual MCP tool name (e.g., "apply_cloud_resource")
|
|
201
|
+
* @param mcpServerSlug - The MCP server slug (e.g., "planton")
|
|
202
|
+
* @param policies - The merged policy map from {@link mergeApprovalPolicies}
|
|
203
|
+
* @returns The policy if approval is required, undefined if auto-approved
|
|
38
204
|
*/
|
|
39
|
-
export declare function mergeApprovalPolicies(resolvedServers: ResolvedMcpServer[], agentOverrides: ToolApprovalOverride[], autoApproveAll: boolean): Map<string, MergedToolPolicy>;
|
|
40
205
|
export declare function lookupMcpToolPolicy(toolName: string, mcpServerSlug: string, policies: Map<string, MergedToolPolicy>): MergedToolPolicy | undefined;
|
|
41
206
|
/**
|
|
42
207
|
* Resolve {{args.field}} placeholders in an approval message using the
|
|
43
208
|
* tool's actual arguments.
|
|
209
|
+
*
|
|
210
|
+
* Placeholder syntax matches the proto-documented format:
|
|
211
|
+
* - {{args.field_name}} — replaced with the argument value
|
|
212
|
+
* - {{tool_name}} — replaced with the tool name
|
|
213
|
+
* - Missing fields are replaced with "<unknown>"
|
|
44
214
|
*/
|
|
45
215
|
export declare function resolveApprovalMessage(template: string, toolName: string, args: Record<string, unknown>): string;
|
|
@@ -5,76 +5,246 @@
|
|
|
5
5
|
* 1. McpServerStatus.tool_approvals — system-generated defaults
|
|
6
6
|
* 2. McpServerSpec.pinned_tool_approvals — manual overrides
|
|
7
7
|
* 3. McpServerUsage.tool_approval_overrides — per-agent customization
|
|
8
|
-
* 4.
|
|
8
|
+
* 4. Active approval leases — the runtime bypass, now SCOPED: the pre-armed
|
|
9
|
+
* spec.auto_approve_all is a whole-run global bypass, while an interactive
|
|
10
|
+
* APPROVE_ALL ("approve all of this kind") grants a run-lifetime lease for
|
|
11
|
+
* only that action's scope (its built-in category, or its MCP server). See
|
|
12
|
+
* {@link ActiveLeases}.
|
|
9
13
|
*
|
|
10
14
|
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent
|
|
11
15
|
* (middleware interruptOn model) to determine which tools need approval.
|
|
12
16
|
*/
|
|
13
|
-
import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
17
|
+
import { ApprovalAction, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
+
import { toolApprovalCategory } from "./tool-kind.js";
|
|
14
19
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* Reduce a single tool call to the scope its APPROVE_ALL would lease — the core
|
|
21
|
+
* of {@link deriveActiveLeases}, extracted so the cross-edition lease-scope
|
|
22
|
+
* corpus (apis/testdata/hitl/lease-scope) can exercise it directly.
|
|
17
23
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* The MCP server slug takes precedence over the built-in category and is used
|
|
25
|
+
* RAW (the server's identity, not case-folded), matching the Go
|
|
26
|
+
* {@link DeriveLeaseScope} and Java {@link LeaseScope.deriveKey} byte-for-byte.
|
|
27
|
+
* The category lookup reuses {@link toolApprovalCategory}, the shared oracle, so
|
|
28
|
+
* a built-in resolves to write/delete/shell (read-only built-ins are ungated and
|
|
29
|
+
* return `undefined`).
|
|
23
30
|
*/
|
|
24
|
-
export function
|
|
31
|
+
export function deriveLeaseScope(toolName, mcpServerSlug) {
|
|
32
|
+
if (mcpServerSlug) {
|
|
33
|
+
return { kind: "server", server: mcpServerSlug };
|
|
34
|
+
}
|
|
35
|
+
const category = toolApprovalCategory(toolName);
|
|
36
|
+
if (category) {
|
|
37
|
+
return { kind: "category", category };
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Derive the active approval leases for an execution.
|
|
43
|
+
*
|
|
44
|
+
* The scoped successor to the former all-or-nothing hasApproveAllDecision:
|
|
45
|
+
* instead of "any APPROVE_ALL anywhere disables the whole gate", each
|
|
46
|
+
* APPROVE_ALL decision is reduced (via {@link deriveLeaseScope}) to the SCOPE of
|
|
47
|
+
* the tool it was made on — the built-in category for a built-in tool (read-only
|
|
48
|
+
* tools are never gated, so a built-in lease is always write/delete/shell), or
|
|
49
|
+
* the MCP server slug for an MCP tool — and only that scope is auto-approved for
|
|
50
|
+
* the rest of the run.
|
|
51
|
+
*
|
|
52
|
+
* Scans root and sub-agent tool calls so a lease granted anywhere applies
|
|
53
|
+
* execution-wide (matching the prior cross-sub-agent behavior, now bounded by
|
|
54
|
+
* scope). Both harnesses call this so the contract is defined in exactly one
|
|
55
|
+
* place. The scope derivation reuses {@link toolApprovalCategory}, the same
|
|
56
|
+
* corpus-tested oracle the Go and Java editions mirror, so the backend's
|
|
57
|
+
* scope-aware bulk-approve and this runner-side evaluation can never disagree.
|
|
58
|
+
*/
|
|
59
|
+
export function deriveActiveLeases(execution) {
|
|
60
|
+
const categories = new Set();
|
|
61
|
+
const servers = new Set();
|
|
62
|
+
const addLease = (tc) => {
|
|
63
|
+
if (tc.approvalAction !== ApprovalAction.APPROVE_ALL)
|
|
64
|
+
return;
|
|
65
|
+
const scope = deriveLeaseScope(tc.name, tc.mcpServerSlug);
|
|
66
|
+
if (!scope)
|
|
67
|
+
return;
|
|
68
|
+
if (scope.kind === "server") {
|
|
69
|
+
servers.add(scope.server);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
categories.add(scope.category);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
25
75
|
const status = execution.status;
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (tc.approvalAction === ApprovalAction.APPROVE_ALL)
|
|
31
|
-
return true;
|
|
76
|
+
if (status) {
|
|
77
|
+
for (const message of status.messages) {
|
|
78
|
+
for (const tc of message.toolCalls)
|
|
79
|
+
addLease(tc);
|
|
32
80
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (tc.approvalAction === ApprovalAction.APPROVE_ALL)
|
|
38
|
-
return true;
|
|
81
|
+
for (const sa of status.subAgentExecutions) {
|
|
82
|
+
for (const message of sa.messages) {
|
|
83
|
+
for (const tc of message.toolCalls)
|
|
84
|
+
addLease(tc);
|
|
39
85
|
}
|
|
40
86
|
}
|
|
41
87
|
}
|
|
42
|
-
return
|
|
88
|
+
return {
|
|
89
|
+
global: execution.spec?.autoApproveAll ?? false,
|
|
90
|
+
categories,
|
|
91
|
+
servers,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Monotonic identifier of the policy-engine logic that produced a decision,
|
|
96
|
+
* persisted on `ToolCall.policy_engine_version`. Bumped when the
|
|
97
|
+
* merge/classification semantics change so decisions made by different engine
|
|
98
|
+
* versions remain distinguishable in audits. Phase 7 made
|
|
99
|
+
* `annotation_destructive_tighten` a distinct, persisted source.
|
|
100
|
+
*/
|
|
101
|
+
export const POLICY_ENGINE_VERSION = "phase-7";
|
|
102
|
+
/**
|
|
103
|
+
* Map the runner-internal {@link PolicySource} to the persisted proto
|
|
104
|
+
* {@link ApprovalPolicySource}. `undefined` (a tool no policy layer governs —
|
|
105
|
+
* e.g. a read-only built-in) maps to UNSPECIFIED, so the persisted field is left
|
|
106
|
+
* at its default exactly as an unclassified `tool_kind` is. The 1:1 mapping keeps
|
|
107
|
+
* the runner's union and the proto enum from drifting (asserted by the
|
|
108
|
+
* cross-edition corpus).
|
|
109
|
+
*/
|
|
110
|
+
export function toProtoPolicySource(source) {
|
|
111
|
+
switch (source) {
|
|
112
|
+
case "classifier_default":
|
|
113
|
+
return ApprovalPolicySource.CLASSIFIER_DEFAULT;
|
|
114
|
+
case "pinned_override":
|
|
115
|
+
return ApprovalPolicySource.PINNED_OVERRIDE;
|
|
116
|
+
case "agent_override":
|
|
117
|
+
return ApprovalPolicySource.AGENT_OVERRIDE;
|
|
118
|
+
case "auto_approve_all":
|
|
119
|
+
return ApprovalPolicySource.AUTO_APPROVE_ALL;
|
|
120
|
+
case "approval_lease":
|
|
121
|
+
return ApprovalPolicySource.APPROVAL_LEASE;
|
|
122
|
+
case "builtin_category":
|
|
123
|
+
return ApprovalPolicySource.BUILTIN_CATEGORY;
|
|
124
|
+
case "annotation_destructive_tighten":
|
|
125
|
+
return ApprovalPolicySource.ANNOTATION_DESTRUCTIVE_TIGHTEN;
|
|
126
|
+
case "file_capture":
|
|
127
|
+
// Capture-mode flow is never persisted on a gated tool call (the file tool
|
|
128
|
+
// is not gated — it has no WAITING_APPROVAL row); it exists only on the
|
|
129
|
+
// audit receipt. Map to UNSPECIFIED for the proto-persisted field.
|
|
130
|
+
return ApprovalPolicySource.UNSPECIFIED;
|
|
131
|
+
case undefined:
|
|
132
|
+
return ApprovalPolicySource.UNSPECIFIED;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Derive the authorization provenance — which policy layer governs this tool —
|
|
137
|
+
* for persisting on `ToolCall.approval_policy_source`.
|
|
138
|
+
*
|
|
139
|
+
* This is the read-side twin of the gate's decision logic: same layered
|
|
140
|
+
* precedence, but it answers "which layer governs this call?" for EVERY tool
|
|
141
|
+
* (gated or auto-approved), so the StatusBuilders can stamp provenance on the
|
|
142
|
+
* tool call exactly where they stamp `tool_kind`. It returns `undefined` for a
|
|
143
|
+
* plain read-only built-in that no policy layer touches (the proto's
|
|
144
|
+
* APPROVAL_POLICY_SOURCE_UNSPECIFIED).
|
|
145
|
+
*
|
|
146
|
+
* Precedence:
|
|
147
|
+
* 1. Whole-run global bypass (pre-armed auto_approve_all) governs everything —
|
|
148
|
+
* it is *why* anything ran ungated, so it wins.
|
|
149
|
+
* 2. MCP tool: the merged policy carries the responsible layer when gated; an
|
|
150
|
+
* absent entry means the four-level chain cleared it (classifier base). A
|
|
151
|
+
* server-scoped lease also surfaces as an absent entry — distinguishing it
|
|
152
|
+
* would need the lease set threaded here and is deferred with the rest of the
|
|
153
|
+
* per-server lease provenance, so a lease-cleared MCP tool reads
|
|
154
|
+
* classifier_default (matching the gate).
|
|
155
|
+
* 3. Built-in: a mutating category is governed (leased → approval_lease, else
|
|
156
|
+
* builtin_category); a read-only built-in is governed by no layer → undefined.
|
|
157
|
+
*/
|
|
158
|
+
export function resolveApprovalProvenance(toolName, serverSlug, policies, leasedCategories, globalBypass) {
|
|
159
|
+
if (globalBypass)
|
|
160
|
+
return "auto_approve_all";
|
|
161
|
+
if (serverSlug) {
|
|
162
|
+
const policy = policies.get(`${serverSlug}/${toolName}`);
|
|
163
|
+
if (policy)
|
|
164
|
+
return policy.source;
|
|
165
|
+
return "classifier_default";
|
|
166
|
+
}
|
|
167
|
+
const category = toolApprovalCategory(toolName);
|
|
168
|
+
if (!category)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (leasedCategories.has(category))
|
|
171
|
+
return "approval_lease";
|
|
172
|
+
return "builtin_category";
|
|
43
173
|
}
|
|
44
174
|
/**
|
|
45
175
|
* Merge approval policies from all four levels into a single lookup map.
|
|
46
176
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
177
|
+
* Each MCP server contributes its own set of policies, so the map is keyed by
|
|
178
|
+
* "serverSlug/toolName" to avoid collisions between servers.
|
|
179
|
+
*
|
|
180
|
+
* Policy chain (each level overrides the previous):
|
|
181
|
+
* 1. status.toolApprovals — system-generated defaults; presence = requires approval
|
|
182
|
+
* 2. spec.pinnedToolApprovals — manual overrides; presence = requires approval
|
|
183
|
+
* 3. agent tool_approval_overrides — explicit boolean per tool (enable OR disable)
|
|
184
|
+
* 4. active leases — runtime bypass (highest priority), now scoped
|
|
185
|
+
*
|
|
186
|
+
* The map carries ONLY the tools that require approval — a tool's absence means
|
|
187
|
+
* "auto-approved". Leases shape that absence:
|
|
188
|
+
* - On a global pre-arm ({@link ActiveLeases.global}) the map is empty.
|
|
189
|
+
* - A server-scoped lease drops that server's tools from the map entirely. This
|
|
190
|
+
* single omission makes EVERY substrate treat the server as auto-approved with
|
|
191
|
+
* no extra code — the deep-agent gate and StatusBuilder read the map, and the
|
|
192
|
+
* Cursor hook's mcpToolPolicies is built from it (the hook is not itself
|
|
193
|
+
* server-aware, so omission is the only way to lease an MCP server there).
|
|
194
|
+
* Built-in CATEGORY leases are NOT applied here — built-ins are not in this map;
|
|
195
|
+
* they are cleared at the gate (deep-agent) and the hook (Cursor) instead.
|
|
196
|
+
*
|
|
197
|
+
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent (middleware
|
|
198
|
+
* interruptOn model), so the four-level semantics are defined in exactly one
|
|
199
|
+
* place.
|
|
49
200
|
*/
|
|
50
|
-
export function mergeApprovalPolicies(resolvedServers, agentOverrides,
|
|
201
|
+
export function mergeApprovalPolicies(resolvedServers, agentOverrides, leases) {
|
|
51
202
|
const merged = new Map();
|
|
52
|
-
if (
|
|
203
|
+
if (leases.global)
|
|
53
204
|
return merged;
|
|
54
205
|
for (const server of resolvedServers) {
|
|
206
|
+
// Server-scoped lease: a prior APPROVE_ALL on one of this server's tools
|
|
207
|
+
// auto-approves the whole server for the run, so none of its tools enter the
|
|
208
|
+
// require-approval map (identical to how an already-auto-approved tool is
|
|
209
|
+
// absent — every consumer treats it as cleared).
|
|
210
|
+
if (leases.servers.has(server.slug))
|
|
211
|
+
continue;
|
|
55
212
|
const serverPolicies = new Map();
|
|
213
|
+
// Layer 1: system-generated defaults (presence = requires approval). A tool
|
|
214
|
+
// the connect-time tightener force-gated from its destructiveHint annotation
|
|
215
|
+
// carries that provenance (from_destructive_hint) so it is attributed to the
|
|
216
|
+
// annotation rather than the classifier — the only sub-case within layer 1.
|
|
56
217
|
for (const policy of server.toolApprovals) {
|
|
57
218
|
if (!policy.toolName)
|
|
58
219
|
continue;
|
|
59
220
|
serverPolicies.set(policy.toolName, {
|
|
60
221
|
requiresApproval: true,
|
|
61
222
|
message: policy.message || `Execute tool: ${policy.toolName}`,
|
|
223
|
+
source: policy.fromDestructiveHint
|
|
224
|
+
? "annotation_destructive_tighten"
|
|
225
|
+
: "classifier_default",
|
|
62
226
|
});
|
|
63
227
|
}
|
|
228
|
+
// Layer 2: manual overrides (presence = requires approval, overrides layer 1).
|
|
64
229
|
for (const pinned of server.pinnedToolApprovals) {
|
|
65
230
|
if (!pinned.toolName)
|
|
66
231
|
continue;
|
|
67
232
|
serverPolicies.set(pinned.toolName, {
|
|
68
233
|
requiresApproval: true,
|
|
69
234
|
message: pinned.message || serverPolicies.get(pinned.toolName)?.message || `Execute tool: ${pinned.toolName}`,
|
|
235
|
+
source: "pinned_override",
|
|
70
236
|
});
|
|
71
237
|
}
|
|
238
|
+
// Layer 3: per-agent overrides (explicit boolean, can enable or disable).
|
|
239
|
+
// Touching a tool here makes the per-agent layer the responsible source,
|
|
240
|
+
// whether it enables, disables, or re-messages the gate.
|
|
72
241
|
for (const override of agentOverrides) {
|
|
73
242
|
if (!override.toolName)
|
|
74
243
|
continue;
|
|
75
244
|
const existing = serverPolicies.get(override.toolName);
|
|
76
245
|
if (existing) {
|
|
77
246
|
existing.requiresApproval = override.requiresApproval;
|
|
247
|
+
existing.source = "agent_override";
|
|
78
248
|
if (override.message) {
|
|
79
249
|
existing.message = override.message;
|
|
80
250
|
}
|
|
@@ -83,9 +253,11 @@ export function mergeApprovalPolicies(resolvedServers, agentOverrides, autoAppro
|
|
|
83
253
|
serverPolicies.set(override.toolName, {
|
|
84
254
|
requiresApproval: true,
|
|
85
255
|
message: override.message || `Execute tool: ${override.toolName}`,
|
|
256
|
+
source: "agent_override",
|
|
86
257
|
});
|
|
87
258
|
}
|
|
88
259
|
}
|
|
260
|
+
// Emit only the tools that still require approval after all layers.
|
|
89
261
|
for (const [toolName, policy] of serverPolicies) {
|
|
90
262
|
if (!policy.requiresApproval)
|
|
91
263
|
continue;
|
|
@@ -95,17 +267,31 @@ export function mergeApprovalPolicies(resolvedServers, agentOverrides, autoAppro
|
|
|
95
267
|
mcpServerSlug: server.slug,
|
|
96
268
|
requiresApproval: true,
|
|
97
269
|
approvalMessage: policy.message,
|
|
270
|
+
source: policy.source,
|
|
98
271
|
});
|
|
99
272
|
}
|
|
100
273
|
}
|
|
101
274
|
return merged;
|
|
102
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Look up whether an MCP tool requires approval.
|
|
278
|
+
*
|
|
279
|
+
* @param toolName - The actual MCP tool name (e.g., "apply_cloud_resource")
|
|
280
|
+
* @param mcpServerSlug - The MCP server slug (e.g., "planton")
|
|
281
|
+
* @param policies - The merged policy map from {@link mergeApprovalPolicies}
|
|
282
|
+
* @returns The policy if approval is required, undefined if auto-approved
|
|
283
|
+
*/
|
|
103
284
|
export function lookupMcpToolPolicy(toolName, mcpServerSlug, policies) {
|
|
104
285
|
return policies.get(`${mcpServerSlug}/${toolName}`);
|
|
105
286
|
}
|
|
106
287
|
/**
|
|
107
288
|
* Resolve {{args.field}} placeholders in an approval message using the
|
|
108
289
|
* tool's actual arguments.
|
|
290
|
+
*
|
|
291
|
+
* Placeholder syntax matches the proto-documented format:
|
|
292
|
+
* - {{args.field_name}} — replaced with the argument value
|
|
293
|
+
* - {{tool_name}} — replaced with the tool name
|
|
294
|
+
* - Missing fields are replaced with "<unknown>"
|
|
109
295
|
*/
|
|
110
296
|
export function resolveApprovalMessage(template, toolName, args) {
|
|
111
297
|
return template
|