@stigmer/runner 3.10.0 → 3.11.1-dev.20260812192248
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/README.md +14 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-llm.js +9 -10
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +2 -1
- package/dist/activities/classify-tool-approvals.js +28 -2
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +39 -0
- package/dist/activities/discover-mcp-server.js +165 -28
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +14 -2
- package/dist/activities/emit-event.js +52 -17
- package/dist/activities/emit-event.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
- package/dist/activities/execute-cursor/approval-state.js +7 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +18 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/env-resolver.js +3 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +59 -3
- package/dist/activities/execute-cursor/error-classifier.js +115 -6
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
- package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
- package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
- package/dist/activities/execute-cursor/hook-script.js +72 -10
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +51 -11
- package/dist/activities/execute-cursor/index.js +177 -93
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
- package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
- package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
- package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
- package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +51 -4
- package/dist/activities/execute-cursor/prompt-builder.js +49 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
- package/dist/activities/execute-cursor/turn-recovery.js +193 -0
- package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
- package/dist/activities/execute-cursor/turn-stream.js +4 -1
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/environment.js +3 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +15 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
- package/dist/activities/execute-deep-agent/setup.js +65 -24
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
- package/dist/activities/execute-deep-agent/shell-env.js +7 -9
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +15 -2
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +27 -6
- package/dist/activities/execute-deep-agent/subagent-wiring.js +26 -5
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -3
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/run-command.d.ts +5 -2
- package/dist/activities/run-command.js +22 -10
- package/dist/activities/run-command.js.map +1 -1
- package/dist/activities/run-env.d.ts +40 -0
- package/dist/activities/run-env.js +66 -0
- package/dist/activities/run-env.js.map +1 -0
- package/dist/activities/workflow-event-activities.d.ts +28 -10
- package/dist/activities/workflow-event-activities.js +87 -58
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +21 -1
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -21
- package/dist/client/stigmer-client.js +106 -29
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +64 -0
- package/dist/encryption/config.js +108 -0
- package/dist/encryption/config.js.map +1 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +3 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/payload-codec.d.ts +41 -0
- package/dist/encryption/payload-codec.js +130 -0
- package/dist/encryption/payload-codec.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +57 -0
- package/dist/middleware/path-normalization.js +109 -0
- package/dist/middleware/path-normalization.js.map +1 -0
- package/dist/middleware/types.d.ts +8 -0
- package/dist/payload-codecs.d.ts +17 -0
- package/dist/payload-codecs.js +42 -0
- package/dist/payload-codecs.js.map +1 -0
- package/dist/preflight.d.ts +31 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -1
- package/dist/runner-manager.js +21 -17
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -18
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +9 -3
- package/dist/shared/approval-policy.js +15 -6
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +23 -1
- package/dist/shared/artifact-storage.js +51 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-naming.d.ts +53 -0
- package/dist/shared/attachment-naming.js +59 -0
- package/dist/shared/attachment-naming.js.map +1 -0
- package/dist/shared/caller-identity.d.ts +23 -2
- package/dist/shared/caller-identity.js +36 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +1 -0
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +26 -1
- package/dist/shared/checkpointer/http-saver.js +54 -8
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.js +1 -0
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/conversation-catchup.d.ts +9 -2
- package/dist/shared/conversation-catchup.js +39 -6
- package/dist/shared/conversation-catchup.js.map +1 -1
- package/dist/shared/datastore-attachment.d.ts +50 -7
- package/dist/shared/datastore-attachment.js +93 -11
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/grpc-retry.d.ts +5 -1
- package/dist/shared/grpc-retry.js +5 -1
- package/dist/shared/grpc-retry.js.map +1 -1
- package/dist/shared/http-retry.d.ts +85 -0
- package/dist/shared/http-retry.js +101 -0
- package/dist/shared/http-retry.js.map +1 -0
- package/dist/shared/llm-backend.d.ts +275 -0
- package/dist/shared/llm-backend.js +425 -0
- package/dist/shared/llm-backend.js.map +1 -0
- package/dist/shared/llm-proxy.d.ts +8 -0
- package/dist/shared/llm-proxy.js +15 -0
- package/dist/shared/llm-proxy.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +57 -0
- package/dist/shared/mcp-enabled-tools.js +86 -0
- package/dist/shared/mcp-enabled-tools.js.map +1 -0
- package/dist/shared/mcp-manager.d.ts +11 -5
- package/dist/shared/mcp-manager.js +47 -8
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +39 -2
- package/dist/shared/mcp-resolver.js +38 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
- package/dist/shared/mcp-schema-sanitizer.js +197 -0
- package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
- package/dist/shared/model-client.d.ts +18 -5
- package/dist/shared/model-client.js +149 -18
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-error.js +198 -5
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/model-pricing-data.d.ts +2 -0
- package/dist/shared/model-pricing-data.js +13 -3
- package/dist/shared/model-pricing-data.js.map +1 -1
- package/dist/shared/model-registry.js +3 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +33 -0
- package/dist/shared/plan-mode-permissions.js +35 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/shared/registry-endpoint.d.ts +20 -0
- package/dist/shared/registry-endpoint.js +25 -0
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +24 -0
- package/dist/shared/runner-credential-keys.js +47 -0
- package/dist/shared/runner-credential-keys.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/resolve.d.ts +19 -1
- package/dist/workflow-engine/resolve.js +37 -2
- package/dist/workflow-engine/resolve.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +18 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
- package/dist/workflows/call-agent-orchestrator.js +1 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +9 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/engine-core.js +23 -2
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +1 -1
- package/dist/workflows/execute-from-execution.js +11 -1
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +9 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +287 -0
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
- package/src/__tests__/history-encryption-e2e.test.ts +243 -0
- package/src/__tests__/preflight.test.ts +65 -2
- package/src/activities/__tests__/call-llm.test.ts +75 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +242 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
- package/src/activities/call-llm.ts +9 -16
- package/src/activities/classify-tool-approvals.ts +34 -4
- package/src/activities/discover-mcp-server.ts +203 -31
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +53 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +151 -16
- package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
- package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
- package/src/activities/execute-cursor/approval-state.ts +30 -1
- package/src/activities/execute-cursor/attachment-resolver.ts +31 -3
- package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
- package/src/activities/execute-cursor/env-resolver.ts +3 -1
- package/src/activities/execute-cursor/error-classifier.ts +131 -7
- package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
- package/src/activities/execute-cursor/hook-script.ts +74 -10
- package/src/activities/execute-cursor/index.ts +209 -109
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +85 -9
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-cursor/turn-stream.ts +5 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +110 -8
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +178 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
- package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
- package/src/activities/execute-deep-agent/environment.ts +3 -1
- package/src/activities/execute-deep-agent/index.ts +20 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
- package/src/activities/execute-deep-agent/setup.ts +85 -29
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +30 -2
- package/src/activities/execute-deep-agent/subagent-wiring.ts +39 -6
- package/src/activities/hydrate-workflow-execution.ts +8 -3
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/bootstrap.ts +5 -0
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +110 -11
- package/src/client/stigmer-client.ts +150 -37
- package/src/encryption/config.ts +155 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/middleware/__tests__/path-normalization.test.ts +140 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/path-normalization.ts +125 -0
- package/src/middleware/types.ts +11 -0
- package/src/payload-codecs.ts +62 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +26 -26
- package/src/runner.ts +12 -27
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/artifact-storage.test.ts +145 -2
- package/src/shared/__tests__/attachment-naming.test.ts +159 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
- package/src/shared/__tests__/caller-identity.test.ts +25 -0
- package/src/shared/__tests__/channel-attachment.test.ts +1 -1
- package/src/shared/__tests__/connect-backfill.test.ts +1 -0
- package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
- package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
- package/src/shared/__tests__/foundry-seam.test.ts +482 -0
- package/src/shared/__tests__/http-retry.test.ts +68 -0
- package/src/shared/__tests__/llm-backend.test.ts +616 -0
- package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
- package/src/shared/__tests__/mcp-manager.test.ts +137 -15
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +200 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/model-pricing.test.ts +62 -2
- package/src/shared/__tests__/model-registry.test.ts +22 -0
- package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
- package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
- package/src/shared/__tests__/vertex-seam.test.ts +295 -0
- package/src/shared/approval-policy.ts +14 -7
- package/src/shared/artifact-storage.ts +75 -10
- package/src/shared/attachment-naming.ts +78 -0
- package/src/shared/caller-identity.ts +40 -5
- package/src/shared/channel-attachment.ts +1 -0
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
- package/src/shared/checkpointer/http-saver.ts +71 -8
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +139 -0
- package/src/shared/llm-backend.ts +544 -0
- package/src/shared/llm-proxy.ts +15 -0
- package/src/shared/mcp-enabled-tools.ts +105 -0
- package/src/shared/mcp-manager.ts +56 -8
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/model-client.ts +179 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +37 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
- package/src/workflows/call-agent-orchestrator.ts +10 -0
- package/src/workflows/connect-mcp-server.ts +9 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
- package/src/workflows/types.ts +8 -0
- package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
- package/dist/activities/execute-cursor/mcp-config.js +0 -39
- package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
|
@@ -45,10 +45,11 @@ function makeScopedTokenExchangeRequest() {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** An unsigned JWT-shaped token carrying the given token_type claim. */
|
|
48
|
-
function fakeTokenOfType(tokenType
|
|
48
|
+
function fakeTokenOfType(tokenType?: string): string {
|
|
49
49
|
const b64 = (obj: Record<string, unknown>) =>
|
|
50
50
|
Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
51
|
-
|
|
51
|
+
const claims = tokenType === undefined ? {} : { token_type: tokenType };
|
|
52
|
+
return `${b64({ alg: "RS256" })}.${b64(claims)}.sig`;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -310,32 +311,82 @@ describe("StigmerClient", () => {
|
|
|
310
311
|
expect(spy).not.toHaveBeenCalled();
|
|
311
312
|
});
|
|
312
313
|
|
|
313
|
-
|
|
314
|
+
// NOTE (oss#535): the no-credential arm previously pinned "skip the
|
|
315
|
+
// exchange" — correct while OSS neither minted nor redacted. Since
|
|
316
|
+
// oss#535 the OSS server redacts EC reads by default and mints
|
|
317
|
+
// execution-scoped tokens on this exchange, so a credential-less runner
|
|
318
|
+
// must now ASK, and fall back to the tokenless read only when the server
|
|
319
|
+
// answers not-minted (pre-oss#535 OSS) or refuses (cloud refusing a
|
|
320
|
+
// non-runner credential — the legacy desktop degrade).
|
|
321
|
+
it("exchanges best-effort when no runner credential exists and uses a minted token (oss#535)", async () => {
|
|
314
322
|
const client = clientWithRunnerCredential(null);
|
|
315
|
-
|
|
323
|
+
vi.spyOn(client, "getRunnerScopedToken").mockResolvedValue({
|
|
324
|
+
token: "oss-scoped-tok",
|
|
325
|
+
expiresInSeconds: 3600,
|
|
326
|
+
});
|
|
316
327
|
|
|
317
328
|
const token = await client.acquireScopedRunnerToken({ workflowExecutionId: "wfx_1" });
|
|
318
329
|
|
|
319
|
-
expect(token).
|
|
320
|
-
expect(
|
|
330
|
+
expect(token).toBe("oss-scoped-tok");
|
|
331
|
+
expect(client.getRunnerScopedToken).toHaveBeenCalledWith({ workflowExecutionId: "wfx_1" });
|
|
321
332
|
});
|
|
322
333
|
|
|
323
|
-
it("
|
|
324
|
-
const client = clientWithRunnerCredential(
|
|
334
|
+
it("proceeds tokenless when a credential-less exchange is answered not-minted (pre-oss#535 server)", async () => {
|
|
335
|
+
const client = clientWithRunnerCredential(null);
|
|
325
336
|
vi.spyOn(client, "getRunnerScopedToken").mockResolvedValue(undefined);
|
|
326
337
|
|
|
338
|
+
const token = await client.acquireScopedRunnerToken({ workflowExecutionId: "wfx_1" });
|
|
339
|
+
|
|
340
|
+
expect(token).toBeUndefined();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("proceeds tokenless when a credential-less exchange is refused (cloud refusing a non-runner credential)", async () => {
|
|
344
|
+
const client = clientWithRunnerCredential(null);
|
|
345
|
+
vi.spyOn(client, "getRunnerScopedToken").mockRejectedValue(new Error("permission denied"));
|
|
346
|
+
|
|
327
347
|
const token = await client.acquireScopedRunnerToken({ agentExecutionId: "aex_1" });
|
|
328
348
|
|
|
329
349
|
expect(token).toBeUndefined();
|
|
330
350
|
});
|
|
331
351
|
|
|
332
|
-
it("
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
it("treats a non-JWT process token as no runner credential (the harness/proxy token shape)", async () => {
|
|
353
|
+
// e.g. the conformance harness boots the runner with a mock proxy
|
|
354
|
+
// token that is not a Stigmer-minted JWT; the best-effort arm must
|
|
355
|
+
// still ask the server for a scoped token.
|
|
356
|
+
const client = clientWithRunnerCredential("not-a-jwt-proxy-token");
|
|
357
|
+
vi.spyOn(client, "getRunnerScopedToken").mockResolvedValue({ token: "scoped-tok" });
|
|
358
|
+
|
|
359
|
+
const token = await client.acquireScopedRunnerToken({ agentExecutionId: "aex_1" });
|
|
360
|
+
|
|
361
|
+
expect(token).toBe("scoped-tok");
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("treats a claim-less user JWT as no runner credential (legacy desktop shape)", async () => {
|
|
365
|
+
const client = clientWithRunnerCredential(fakeTokenOfType(undefined));
|
|
366
|
+
vi.spyOn(client, "getRunnerScopedToken").mockResolvedValue(undefined);
|
|
335
367
|
|
|
336
368
|
const token = await client.acquireScopedRunnerToken({ agentExecutionId: "aex_1" });
|
|
337
369
|
|
|
338
370
|
expect(token).toBeUndefined();
|
|
371
|
+
expect(client.getRunnerScopedToken).toHaveBeenCalled();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("throws when the server mints no token — the bootstrap credential no longer decrypts (stigmer-cloud#218)", async () => {
|
|
375
|
+
const client = clientWithRunnerCredential(fakeTokenOfType("embedded_runner"));
|
|
376
|
+
vi.spyOn(client, "getRunnerScopedToken").mockResolvedValue(undefined);
|
|
377
|
+
|
|
378
|
+
await expect(
|
|
379
|
+
client.acquireScopedRunnerToken({ agentExecutionId: "aex_1" }),
|
|
380
|
+
).rejects.toThrow(/minted no scoped runner token for agent execution aex_1/);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("throws when the exchange fails, naming the scope and preserving the cause", async () => {
|
|
384
|
+
const client = clientWithRunnerCredential(fakeTokenOfType("embedded_runner"));
|
|
385
|
+
vi.spyOn(client, "getRunnerScopedToken").mockRejectedValue(new Error("boom"));
|
|
386
|
+
|
|
387
|
+
await expect(
|
|
388
|
+
client.acquireScopedRunnerToken({ workflowExecutionId: "wfx_1" }),
|
|
389
|
+
).rejects.toThrow(/exchange failed for workflow execution wfx_1: boom/);
|
|
339
390
|
});
|
|
340
391
|
});
|
|
341
392
|
|
|
@@ -432,6 +483,54 @@ describe("StigmerClient", () => {
|
|
|
432
483
|
});
|
|
433
484
|
});
|
|
434
485
|
|
|
486
|
+
describe("sendWorkflowSignal", () => {
|
|
487
|
+
function clientWithWorkflowCommand() {
|
|
488
|
+
const client = new StigmerClient({ endpoint: "http://localhost", token: null });
|
|
489
|
+
const sendSignal = vi.fn().mockResolvedValue({});
|
|
490
|
+
// Reach into the private generated client: the mocked createClient()
|
|
491
|
+
// returned {}, so install the method it would have provided.
|
|
492
|
+
(client as any).workflowExecutionCommand = { sendSignal };
|
|
493
|
+
return { client, rpc: sendSignal };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
it("builds SendSignalInput with execution addressing and the payload verbatim", async () => {
|
|
497
|
+
const { client, rpc } = clientWithWorkflowCommand();
|
|
498
|
+
const payload = { type: "approval.granted", data: { ok: true } };
|
|
499
|
+
|
|
500
|
+
await client.sendWorkflowSignal("wfx_1", "approval_resolved", payload);
|
|
501
|
+
|
|
502
|
+
const input = rpc.mock.calls[0]![0];
|
|
503
|
+
expect(input.executionId).toBe("wfx_1");
|
|
504
|
+
expect(input.signalName).toBe("approval_resolved");
|
|
505
|
+
expect(input.payload).toEqual(payload);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("sends no idempotency key (oss#442: failed-delivery claims are never released)", async () => {
|
|
509
|
+
const { client, rpc } = clientWithWorkflowCommand();
|
|
510
|
+
|
|
511
|
+
await client.sendWorkflowSignal("wfx_1", "ping", undefined);
|
|
512
|
+
|
|
513
|
+
const input = rpc.mock.calls[0]![0];
|
|
514
|
+
expect(input.idempotencyKey).toBe("");
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("passes the per-call timeout through to the RPC", async () => {
|
|
518
|
+
const { client, rpc } = clientWithWorkflowCommand();
|
|
519
|
+
|
|
520
|
+
await client.sendWorkflowSignal("wfx_1", "ping", undefined, { timeoutMs: 30_000 });
|
|
521
|
+
|
|
522
|
+
expect(rpc).toHaveBeenCalledWith(expect.anything(), { timeoutMs: 30_000 });
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("passes an undefined timeout when no options are supplied", async () => {
|
|
526
|
+
const { client, rpc } = clientWithWorkflowCommand();
|
|
527
|
+
|
|
528
|
+
await client.sendWorkflowSignal("wfx_1", "ping", undefined);
|
|
529
|
+
|
|
530
|
+
expect(rpc).toHaveBeenCalledWith(expect.anything(), { timeoutMs: undefined });
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
435
534
|
describe("updateToken", () => {
|
|
436
535
|
it("does not affect tokenRef-based resolution", async () => {
|
|
437
536
|
const ref: TokenRef = { current: "from-ref" };
|
|
@@ -44,7 +44,8 @@ import type { UpdateStatusResponse } from "@stigmer/protos/ai/stigmer/agentic/ag
|
|
|
44
44
|
import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
|
|
45
45
|
import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
|
|
46
46
|
import type { WorkflowExecution, WorkflowExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
47
|
-
import { WorkflowExecutionUpdateStatusInputSchema, GetEventLogRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
47
|
+
import { WorkflowExecutionUpdateStatusInputSchema, GetEventLogRequestSchema, SendSignalInputSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
48
|
+
import type { JsonObject } from "@bufbuild/protobuf";
|
|
48
49
|
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
49
50
|
import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
50
51
|
import { WorkflowInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/query_pb";
|
|
@@ -52,9 +53,29 @@ import type { WorkflowInstance } from "@stigmer/protos/ai/stigmer/agentic/workfl
|
|
|
52
53
|
import { PlatformQueryController, GetRunnerScopedTokenInputSchema, TokenRenewalSchema } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
53
54
|
import { ChannelMessageQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_query_pb";
|
|
54
55
|
import type { ChannelTemplate, MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
55
|
-
import {
|
|
56
|
+
import { TOKEN_TYPE_EMBEDDED_RUNNER, tokenTypeOf } from "./token-claims.js";
|
|
56
57
|
import { assertCreateRequirements, assertReferenceRequirements } from "./server-contracts.js";
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Server-managed Temporal payload-encryption keys for this runner.
|
|
61
|
+
*
|
|
62
|
+
* Minted once per caller identity and persisted by the control plane, so
|
|
63
|
+
* every boot of the same identity's runners receives the SAME key — the
|
|
64
|
+
* persistence Temporal replay requires across runner restarts. The secondary
|
|
65
|
+
* pair is present only during a rotation window (decrypt-only; new payloads
|
|
66
|
+
* are written under the primary).
|
|
67
|
+
*/
|
|
68
|
+
export interface BootstrapPayloadEncryptionKeys {
|
|
69
|
+
/** Base64-encoded 32-byte AES-256 key. */
|
|
70
|
+
key: string;
|
|
71
|
+
/** Key id stamped on payloads encrypted under {@link key}. */
|
|
72
|
+
keyId: string;
|
|
73
|
+
/** Previous key of this identity, present during rotation windows. */
|
|
74
|
+
secondaryKey?: string;
|
|
75
|
+
/** Key id of {@link secondaryKey}; present exactly when it is. */
|
|
76
|
+
secondaryKeyId?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
/**
|
|
59
80
|
* Everything an embedded runner needs to bootstrap: Temporal coordinates plus
|
|
60
81
|
* its own minted access token.
|
|
@@ -62,7 +83,10 @@ import { assertCreateRequirements, assertReferenceRequirements } from "./server-
|
|
|
62
83
|
* The Temporal coordinates are always present. The access token is optional: the
|
|
63
84
|
* server only mints one when it has a Cursor proxy to authenticate against
|
|
64
85
|
* (cloud) and a signing key configured — OSS and misconfigured servers omit it,
|
|
65
|
-
* and the runner keeps using its existing token in that case.
|
|
86
|
+
* and the runner keeps using its existing token in that case. Payload-encryption
|
|
87
|
+
* keys follow the same presence contract: absent when the server does not manage
|
|
88
|
+
* runner keys (OSS uses env-configured keys; older cloud servers predate key
|
|
89
|
+
* management).
|
|
66
90
|
*/
|
|
67
91
|
export interface RunnerBootstrapConfig {
|
|
68
92
|
temporalAddress: string;
|
|
@@ -71,15 +95,18 @@ export interface RunnerBootstrapConfig {
|
|
|
71
95
|
runnerAccessToken?: string;
|
|
72
96
|
/** Lifetime of {@link runnerAccessToken} in seconds; absent/0 when no token. */
|
|
73
97
|
runnerAccessTokenExpiresInSeconds?: number;
|
|
98
|
+
/** Server-managed payload-encryption keys; absent when not managed by the server. */
|
|
99
|
+
payloadEncryption?: BootstrapPayloadEncryptionKeys;
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
/**
|
|
77
103
|
* A runner token scoped to one unit of dispatched work (issue #156).
|
|
78
104
|
*
|
|
79
|
-
* Minted on demand by the control plane
|
|
80
|
-
* bootstrap credential
|
|
81
|
-
*
|
|
82
|
-
*
|
|
105
|
+
* Minted on demand by the control plane at task start — a desktop runner
|
|
106
|
+
* exchanges its bootstrap credential, an OSS runner asks with no credential
|
|
107
|
+
* at all (oss#535) — and presented for the ExecutionContext fetch of exactly
|
|
108
|
+
* that execution. Absent when the server does not mint (pre-oss#535 OSS, or
|
|
109
|
+
* a refused credential class) — the runner keeps its existing credential.
|
|
83
110
|
*/
|
|
84
111
|
export interface RunnerScopedToken {
|
|
85
112
|
token: string;
|
|
@@ -272,6 +299,18 @@ export class StigmerClient {
|
|
|
272
299
|
runnerAccessToken: res.runnerAccessToken || undefined,
|
|
273
300
|
runnerAccessTokenExpiresInSeconds:
|
|
274
301
|
res.runnerAccessTokenExpiresInSeconds || undefined,
|
|
302
|
+
// Same presence contract, grouped so callers make ONE presence check.
|
|
303
|
+
// Key without id (or vice versa) is a server contract violation the
|
|
304
|
+
// encryption config loader rejects — not silently dropped here, so the
|
|
305
|
+
// breakage is diagnosable at the fail-closed boundary.
|
|
306
|
+
payloadEncryption: res.payloadEncryptionKey
|
|
307
|
+
? {
|
|
308
|
+
key: res.payloadEncryptionKey,
|
|
309
|
+
keyId: res.payloadEncryptionKeyId,
|
|
310
|
+
secondaryKey: res.payloadEncryptionSecondaryKey || undefined,
|
|
311
|
+
secondaryKeyId: res.payloadEncryptionSecondaryKeyId || undefined,
|
|
312
|
+
}
|
|
313
|
+
: undefined,
|
|
275
314
|
};
|
|
276
315
|
}
|
|
277
316
|
|
|
@@ -313,14 +352,16 @@ export class StigmerClient {
|
|
|
313
352
|
}
|
|
314
353
|
|
|
315
354
|
/**
|
|
316
|
-
* Exchange this runner's
|
|
317
|
-
*
|
|
355
|
+
* Exchange this runner's credential for a token scoped to one unit of
|
|
356
|
+
* dispatched work (issue #156).
|
|
318
357
|
*
|
|
319
|
-
* The call authenticates with the runner credential (see the interceptor)
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* sandbox token a cloud sandbox runner receives at provisioning.
|
|
323
|
-
*
|
|
358
|
+
* The call authenticates with the runner credential (see the interceptor).
|
|
359
|
+
* Cloud verifies it is an embedded_runner token and that its identity can
|
|
360
|
+
* view the named execution, then mints the same session/execution-scoped
|
|
361
|
+
* sandbox token a cloud sandbox runner receives at provisioning. OSS mints
|
|
362
|
+
* an execution-scoped token for any caller (oss#535 — a lane discriminator
|
|
363
|
+
* on a single-user server, not a trust boundary). Returns undefined when
|
|
364
|
+
* the server does not mint (pre-oss#535 OSS, or an unserved scope arm) —
|
|
324
365
|
* presence-based, like the bootstrap token fields.
|
|
325
366
|
*
|
|
326
367
|
* `callerToken` authenticates the exchange per-call instead of the
|
|
@@ -354,41 +395,83 @@ export class StigmerClient {
|
|
|
354
395
|
* Acquire a scoped runner token for an ExecutionContext read, if this
|
|
355
396
|
* runner's credential situation calls for one.
|
|
356
397
|
*
|
|
357
|
-
* The gate is the credential itself
|
|
358
|
-
* bootstrap token needs exchanging. A cloud sandbox runner's credential is
|
|
359
|
-
* already scoped (skip — the exchange would rightly refuse it), and an
|
|
360
|
-
* OSS/local runner holds no runner-class credential at all (skip — the
|
|
361
|
-
* server neither mints nor redacts).
|
|
398
|
+
* The gate is the credential itself, three ways:
|
|
362
399
|
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
400
|
+
* 1. An unscoped embedded_runner bootstrap token MUST be exchanged, and a
|
|
401
|
+
* failed exchange is a hard error, not a fallback: since the #156
|
|
402
|
+
* item-3 flip (stigmer-cloud#218) the bootstrap credential no longer
|
|
403
|
+
* decrypts, so a read that "fell back" would silently receive redacted
|
|
404
|
+
* placeholders and the execution would run against junk secret values —
|
|
405
|
+
* strictly worse than failing here with the real reason. The
|
|
406
|
+
* secret-delivery call sites let this error fail the activity;
|
|
407
|
+
* opportunistic consumers that can genuinely proceed without a scoped
|
|
408
|
+
* token (attachment credential, channel discovery) catch it at the
|
|
409
|
+
* call site, where their degrade-to-empty contract lives.
|
|
410
|
+
*
|
|
411
|
+
* 2. Any other runner-class credential is already scoped (a cloud
|
|
412
|
+
* sandbox/pool/connect token) — skip; the exchange would rightly
|
|
413
|
+
* refuse it and the ambient credential decrypts on its own.
|
|
414
|
+
*
|
|
415
|
+
* 3. No runner-class credential at all (OSS/local, where the process
|
|
416
|
+
* token is absent or carries no token_type claim) — attempt the
|
|
417
|
+
* exchange best-effort (oss#535): a current OSS server mints an
|
|
418
|
+
* execution-scoped token here, which is the ONLY way this runner can
|
|
419
|
+
* read decrypted secrets from its redact-by-default EC RPCs. A server
|
|
420
|
+
* that answers "not minted" (pre-oss#535 OSS, which also does not
|
|
421
|
+
* redact) or refuses the exchange (cloud refusing a non-runner
|
|
422
|
+
* credential — the legacy desktop degrade, which reads redacted today
|
|
423
|
+
* regardless) falls through to the tokenless read, preserving each
|
|
424
|
+
* old pairing's exact behavior.
|
|
367
425
|
*/
|
|
368
426
|
async acquireScopedRunnerToken(
|
|
369
427
|
scope: RunnerScopedTokenScope,
|
|
370
428
|
): Promise<string | undefined> {
|
|
371
|
-
|
|
429
|
+
// Inspect the same credential chain the EC read's interceptor would use.
|
|
430
|
+
const ambientTokenType = tokenTypeOf(
|
|
431
|
+
this.runnerTokenRef?.current ?? this.tokenRef?.current ?? this.currentToken,
|
|
432
|
+
);
|
|
433
|
+
const isEmbeddedRunner = ambientTokenType === TOKEN_TYPE_EMBEDDED_RUNNER;
|
|
434
|
+
if (ambientTokenType !== undefined && !isEmbeddedRunner) {
|
|
435
|
+
// Case 2: an already-scoped runner-class credential.
|
|
372
436
|
return undefined;
|
|
373
437
|
}
|
|
438
|
+
const scopeDescription =
|
|
439
|
+
"agentExecutionId" in scope
|
|
440
|
+
? `agent execution ${scope.agentExecutionId}`
|
|
441
|
+
: "workflowExecutionId" in scope
|
|
442
|
+
? `workflow execution ${scope.workflowExecutionId}`
|
|
443
|
+
: JSON.stringify(scope);
|
|
444
|
+
let scoped: RunnerScopedToken | undefined;
|
|
374
445
|
try {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
);
|
|
446
|
+
scoped = await this.getRunnerScopedToken(scope);
|
|
447
|
+
} catch (err) {
|
|
448
|
+
if (!isEmbeddedRunner) {
|
|
449
|
+
// Case 3: best-effort — a refusal means the server does not serve
|
|
450
|
+
// this credential class; the tokenless read is today's behavior.
|
|
381
451
|
return undefined;
|
|
382
452
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Scoped runner token exchange failed for ${scopeDescription}: ` +
|
|
455
|
+
`${err instanceof Error ? err.message : String(err)}. ` +
|
|
456
|
+
"The bootstrap credential cannot read ExecutionContext secrets " +
|
|
457
|
+
"(stigmer-cloud#218), so this runner cannot serve the execution " +
|
|
458
|
+
"until the exchange succeeds.",
|
|
389
459
|
);
|
|
390
|
-
return undefined;
|
|
391
460
|
}
|
|
461
|
+
if (!scoped) {
|
|
462
|
+
if (!isEmbeddedRunner) {
|
|
463
|
+
// Case 3: a server that mints nothing for this scope also does not
|
|
464
|
+
// redact for this runner (pre-oss#535 OSS) — proceed tokenless.
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
throw new Error(
|
|
468
|
+
`Server minted no scoped runner token for ${scopeDescription}. ` +
|
|
469
|
+
"The bootstrap credential cannot read ExecutionContext secrets " +
|
|
470
|
+
"(stigmer-cloud#218), so this runner cannot serve the execution " +
|
|
471
|
+
"until the control plane mints scoped tokens.",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return scoped.token;
|
|
392
475
|
}
|
|
393
476
|
|
|
394
477
|
/**
|
|
@@ -538,6 +621,36 @@ export class StigmerClient {
|
|
|
538
621
|
return this.workflowExecutionCommand.updateStatus(input);
|
|
539
622
|
}
|
|
540
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Deliver a named signal to another workflow execution through the
|
|
626
|
+
* server's SendSignal lane (validate → phase gate → relay via the
|
|
627
|
+
* outer orchestrator). This is the ONLY sanctioned path for
|
|
628
|
+
* runner-originated signals: a direct Temporal client would bypass
|
|
629
|
+
* both the authorization boundary and the payload-encryption design
|
|
630
|
+
* (per-identity runner keys make sender-side encryption fail closed
|
|
631
|
+
* at receivers holding a different key — see oss#517).
|
|
632
|
+
*
|
|
633
|
+
* Deliberately sends no idempotency_key: the server's dedupe claim is
|
|
634
|
+
* not released when delivery fails (oss#442), so a key here would
|
|
635
|
+
* poison the exact retry it exists to protect — and emit envelope ids
|
|
636
|
+
* regenerate per activity attempt anyway. Revisit once oss#442 lands.
|
|
637
|
+
*/
|
|
638
|
+
async sendWorkflowSignal(
|
|
639
|
+
executionId: string,
|
|
640
|
+
signalName: string,
|
|
641
|
+
payload: JsonObject | undefined,
|
|
642
|
+
options?: { timeoutMs?: number },
|
|
643
|
+
): Promise<WorkflowExecution> {
|
|
644
|
+
const input = create(SendSignalInputSchema, {
|
|
645
|
+
executionId,
|
|
646
|
+
signalName,
|
|
647
|
+
payload,
|
|
648
|
+
});
|
|
649
|
+
return this.workflowExecutionCommand.sendSignal(input, {
|
|
650
|
+
timeoutMs: options?.timeoutMs,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
541
654
|
async getWorkflowExecution(executionId: string): Promise<WorkflowExecution> {
|
|
542
655
|
return this.workflowExecutionQuery.get({ value: executionId });
|
|
543
656
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payload-encryption configuration (stigmer-cloud#227, stigmer#398).
|
|
3
|
+
*
|
|
4
|
+
* Two key sources, strict precedence:
|
|
5
|
+
*
|
|
6
|
+
* 1. Environment (STIGMER_PAYLOAD_ENCRYPTION_KEY(_ID)) — the operator's
|
|
7
|
+
* explicit choice: self-hosted deployments sharing one key with their
|
|
8
|
+
* server, and cloud sandboxes injected with the platform key. When the
|
|
9
|
+
* env key is set, bootstrap-delivered material is ignored entirely.
|
|
10
|
+
* 2. Bootstrap-delivered — server-managed per-identity keys handed to
|
|
11
|
+
* desktop-class runners by getRunnerBootstrapConfig. Held in memory
|
|
12
|
+
* only; persistence lives server-side, which is what makes Temporal
|
|
13
|
+
* replay work across runner restarts (every boot re-fetches the SAME
|
|
14
|
+
* key).
|
|
15
|
+
*
|
|
16
|
+
* Encryption is enabled iff a key is present from either source — the same
|
|
17
|
+
* enabled-iff-configured pattern as the claim-check codec. A malformed key
|
|
18
|
+
* fails the boot rather than silently running plaintext: an operator who set
|
|
19
|
+
* the key (or a server that minted one) intended history to be encrypted.
|
|
20
|
+
*
|
|
21
|
+
* Key rotation: payloads carry the id of the key that encrypted them.
|
|
22
|
+
* During a rotation window the previous key stays readable via the
|
|
23
|
+
* secondary pair while new payloads are written under the primary key.
|
|
24
|
+
* Workers capture keys at construction, so a rotated bootstrap key lands
|
|
25
|
+
* on the next runner boot — there is no live re-key.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface EncryptionKey {
|
|
29
|
+
readonly keyId: string;
|
|
30
|
+
/** 32-byte AES-256 key. */
|
|
31
|
+
readonly key: Buffer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PayloadEncryptionConfig {
|
|
35
|
+
/** Key used to encrypt outgoing payloads (and decrypt its own). */
|
|
36
|
+
readonly primary: EncryptionKey;
|
|
37
|
+
/** Decrypt-only key accepted during rotation windows. */
|
|
38
|
+
readonly secondary?: EncryptionKey;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Server-managed key material delivered by getRunnerBootstrapConfig.
|
|
43
|
+
* Structurally mirrors {@link BootstrapPayloadEncryptionKeys} in
|
|
44
|
+
* stigmer-client.ts — declared here so this leaf module stays free of
|
|
45
|
+
* client imports.
|
|
46
|
+
*/
|
|
47
|
+
export interface BootstrapKeyMaterial {
|
|
48
|
+
readonly key: string;
|
|
49
|
+
readonly keyId?: string;
|
|
50
|
+
readonly secondaryKey?: string;
|
|
51
|
+
readonly secondaryKeyId?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const KEY_ENV = "STIGMER_PAYLOAD_ENCRYPTION_KEY";
|
|
55
|
+
const KEY_ID_ENV = "STIGMER_PAYLOAD_ENCRYPTION_KEY_ID";
|
|
56
|
+
const SECONDARY_KEY_ENV = "STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY";
|
|
57
|
+
const SECONDARY_KEY_ID_ENV = "STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY_ID";
|
|
58
|
+
|
|
59
|
+
const AES_256_KEY_BYTES = 32;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns the encryption config, or undefined when encryption is not
|
|
63
|
+
* configured (the codec is then simply not installed).
|
|
64
|
+
*
|
|
65
|
+
* Source precedence: the env key wins outright; bootstrap-delivered
|
|
66
|
+
* material applies only when no env key is set (see the module doc).
|
|
67
|
+
*
|
|
68
|
+
* @throws when a key is present but malformed, or a key id is missing —
|
|
69
|
+
* key misconfiguration must stop the boot, not degrade to plaintext.
|
|
70
|
+
* This applies equally to bootstrap material: a server that hands out
|
|
71
|
+
* a bad key or omits its id has broken the protocol contract, and
|
|
72
|
+
* running plaintext against a server that manages keys would silently
|
|
73
|
+
* defeat the feature.
|
|
74
|
+
*/
|
|
75
|
+
export function loadPayloadEncryptionConfig(
|
|
76
|
+
bootstrap?: BootstrapKeyMaterial,
|
|
77
|
+
): PayloadEncryptionConfig | undefined {
|
|
78
|
+
const rawKey = process.env[KEY_ENV];
|
|
79
|
+
if (rawKey) {
|
|
80
|
+
const primary: EncryptionKey = {
|
|
81
|
+
keyId: requireKeyId(KEY_ID_ENV),
|
|
82
|
+
key: parseKey(rawKey, KEY_ENV),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const rawSecondary = process.env[SECONDARY_KEY_ENV];
|
|
86
|
+
const secondary: EncryptionKey | undefined = rawSecondary
|
|
87
|
+
? {
|
|
88
|
+
keyId: requireKeyId(SECONDARY_KEY_ID_ENV),
|
|
89
|
+
key: parseKey(rawSecondary, SECONDARY_KEY_ENV),
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
return { primary, secondary };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (bootstrap?.key) {
|
|
97
|
+
const primary: EncryptionKey = {
|
|
98
|
+
keyId: requireBootstrapKeyId(bootstrap.keyId, "payload_encryption_key_id"),
|
|
99
|
+
key: parseKey(bootstrap.key, "bootstrap payload_encryption_key"),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const secondary: EncryptionKey | undefined = bootstrap.secondaryKey
|
|
103
|
+
? {
|
|
104
|
+
keyId: requireBootstrapKeyId(
|
|
105
|
+
bootstrap.secondaryKeyId,
|
|
106
|
+
"payload_encryption_secondary_key_id",
|
|
107
|
+
),
|
|
108
|
+
key: parseKey(bootstrap.secondaryKey, "bootstrap payload_encryption_secondary_key"),
|
|
109
|
+
}
|
|
110
|
+
: undefined;
|
|
111
|
+
|
|
112
|
+
return { primary, secondary };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function requireKeyId(envName: string): string {
|
|
119
|
+
const keyId = process.env[envName];
|
|
120
|
+
// An explicit id is required (no default): during rotation two keys
|
|
121
|
+
// coexist, and payloads must name which one encrypted them.
|
|
122
|
+
if (!keyId) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Payload encryption misconfigured: ${envName} is required when the ` +
|
|
125
|
+
`corresponding key is set`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return keyId;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function requireBootstrapKeyId(keyId: string | undefined, fieldName: string): string {
|
|
132
|
+
if (!keyId) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Runner bootstrap returned a payload encryption key without its ${fieldName} — ` +
|
|
135
|
+
`refusing to encrypt under an unidentified key (server contract violation)`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return keyId;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseKey(rawBase64: string, envName: string): Buffer {
|
|
142
|
+
let key: Buffer;
|
|
143
|
+
try {
|
|
144
|
+
key = Buffer.from(rawBase64, "base64");
|
|
145
|
+
} catch {
|
|
146
|
+
throw new Error(`Payload encryption misconfigured: ${envName} is not valid base64`);
|
|
147
|
+
}
|
|
148
|
+
if (key.length !== AES_256_KEY_BYTES) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Payload encryption misconfigured: ${envName} must decode to ` +
|
|
151
|
+
`${AES_256_KEY_BYTES} bytes (AES-256), got ${key.length}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return key;
|
|
155
|
+
}
|