@stigmer/server 3.15.3-dev.20260916211208 → 3.16.1-dev.20260917224426
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/authorization/lane-admission.d.ts +50 -0
- package/dist/authorization/lane-admission.d.ts.map +1 -0
- package/dist/authorization/lane-admission.js +13 -0
- package/dist/authorization/lane-admission.js.map +1 -0
- package/dist/authorization/schedule-fire-caller.d.ts +2 -2
- package/dist/authorization/schedule-fire-caller.d.ts.map +1 -1
- package/dist/authorization/schedule-fire-caller.js +8 -8
- package/dist/authorization/schedule-fire-caller.js.map +1 -1
- package/dist/boot/compose.d.ts +12 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +68 -14
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +6 -1
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/executioncontext/resolve-values-for-caller.d.ts +33 -14
- package/dist/domain/executioncontext/resolve-values-for-caller.d.ts.map +1 -1
- package/dist/domain/executioncontext/resolve-values-for-caller.js +32 -8
- package/dist/domain/executioncontext/resolve-values-for-caller.js.map +1 -1
- package/dist/domain/identityaccount/actor.d.ts +33 -18
- package/dist/domain/identityaccount/actor.d.ts.map +1 -1
- package/dist/domain/identityaccount/actor.js +20 -3
- package/dist/domain/identityaccount/actor.js.map +1 -1
- package/dist/domain/identityaccount/resolve.d.ts +19 -0
- package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
- package/dist/domain/identityaccount/resolve.js +12 -0
- package/dist/domain/identityaccount/resolve.js.map +1 -1
- package/dist/domain/mcpserver/connect-execution-id.d.ts +5 -0
- package/dist/domain/mcpserver/connect-execution-id.d.ts.map +1 -0
- package/dist/domain/mcpserver/connect-execution-id.js +32 -0
- package/dist/domain/mcpserver/connect-execution-id.js.map +1 -0
- package/dist/domain/mcpserver/connect.d.ts +28 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +27 -7
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/engine.d.ts +9 -3
- package/dist/domain/mcpserver/engine.d.ts.map +1 -1
- package/dist/domain/mcpserver/engine.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +1 -1
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/platform/controller.d.ts +31 -11
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -3
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/plugin/materialize/agent.d.ts +10 -1
- package/dist/domain/plugin/materialize/agent.d.ts.map +1 -1
- package/dist/domain/plugin/materialize/agent.js +35 -1
- package/dist/domain/plugin/materialize/agent.js.map +1 -1
- package/dist/domain/plugin/materialize/plan.d.ts.map +1 -1
- package/dist/domain/plugin/materialize/plan.js +1 -1
- package/dist/domain/plugin/materialize/plan.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.d.ts.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +4 -2
- package/dist/domain/plugin/overlay/sanitize.js.map +1 -1
- package/dist/domain/project/reconcile.d.ts.map +1 -1
- package/dist/domain/project/reconcile.js +14 -10
- package/dist/domain/project/reconcile.js.map +1 -1
- package/dist/domain/workflowexecution/phases.d.ts +21 -0
- package/dist/domain/workflowexecution/phases.d.ts.map +1 -0
- package/dist/domain/workflowexecution/phases.js +31 -0
- package/dist/domain/workflowexecution/phases.js.map +1 -0
- package/dist/identity/oidc-verifier.js +8 -1
- package/dist/identity/oidc-verifier.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +15 -0
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +34 -0
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +5 -3
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/runnerauth/bound-execution.d.ts +27 -0
- package/dist/runnerauth/bound-execution.d.ts.map +1 -0
- package/dist/runnerauth/bound-execution.js +137 -0
- package/dist/runnerauth/bound-execution.js.map +1 -0
- package/dist/runnerauth/built-in-runner-credential-provider.d.ts +28 -0
- package/dist/runnerauth/built-in-runner-credential-provider.d.ts.map +1 -0
- package/dist/runnerauth/built-in-runner-credential-provider.js +217 -0
- package/dist/runnerauth/built-in-runner-credential-provider.js.map +1 -0
- package/dist/runnerauth/constants.d.ts +63 -0
- package/dist/runnerauth/constants.d.ts.map +1 -0
- package/dist/runnerauth/constants.js +63 -0
- package/dist/runnerauth/constants.js.map +1 -0
- package/dist/runnerauth/dispatch-credential.d.ts +38 -0
- package/dist/runnerauth/dispatch-credential.d.ts.map +1 -0
- package/dist/runnerauth/dispatch-credential.js +17 -0
- package/dist/runnerauth/dispatch-credential.js.map +1 -0
- package/dist/runnerauth/runner-credential-provider.d.ts +49 -11
- package/dist/runnerauth/runner-credential-provider.d.ts.map +1 -1
- package/dist/runnerauth/runner-credential-provider.js +16 -4
- package/dist/runnerauth/runner-credential-provider.js.map +1 -1
- package/dist/runnerauth/runner-subject-verifier.d.ts +14 -0
- package/dist/runnerauth/runner-subject-verifier.d.ts.map +1 -0
- package/dist/runnerauth/runner-subject-verifier.js +115 -0
- package/dist/runnerauth/runner-subject-verifier.js.map +1 -0
- package/dist/runnerauth/runnerauth.d.ts +33 -4
- package/dist/runnerauth/runnerauth.d.ts.map +1 -1
- package/dist/runnerauth/runnerauth.js +131 -35
- package/dist/runnerauth/runnerauth.js.map +1 -1
- package/dist/temporal/agentexecution/engine-client.d.ts +13 -0
- package/dist/temporal/agentexecution/engine-client.d.ts.map +1 -1
- package/dist/temporal/agentexecution/engine-client.js +10 -2
- package/dist/temporal/agentexecution/engine-client.js.map +1 -1
- package/dist/temporal/agentexecution/workflow-input.d.ts +13 -0
- package/dist/temporal/agentexecution/workflow-input.d.ts.map +1 -1
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.d.ts.map +1 -1
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js +35 -31
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js.map +1 -1
- package/dist/temporal/workflowexecution/engine-client.d.ts +24 -0
- package/dist/temporal/workflowexecution/engine-client.d.ts.map +1 -1
- package/dist/temporal/workflowexecution/engine-client.js +26 -19
- package/dist/temporal/workflowexecution/engine-client.js.map +1 -1
- package/dist/temporal/workflowexecution/workflow-input.d.ts +14 -0
- package/dist/temporal/workflowexecution/workflow-input.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/authorization/__tests__/lane-admission.test.ts +115 -0
- package/src/authorization/lane-admission.ts +73 -0
- package/src/authorization/schedule-fire-caller.ts +10 -14
- package/src/boot/compose.ts +93 -22
- package/src/boot/inprocess.ts +7 -1
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +104 -0
- package/src/domain/executioncontext/__tests__/resolve-values-capability.test.ts +33 -4
- package/src/domain/executioncontext/resolve-values-for-caller.ts +79 -22
- package/src/domain/identityaccount/__tests__/account-for-stamp.test.ts +111 -0
- package/src/domain/identityaccount/actor.ts +61 -21
- package/src/domain/identityaccount/resolve.ts +31 -0
- package/src/domain/mcpserver/__tests__/connect.test.ts +17 -2
- package/src/domain/mcpserver/connect-execution-id.ts +34 -0
- package/src/domain/mcpserver/connect.ts +38 -3
- package/src/domain/mcpserver/engine.ts +9 -3
- package/src/domain/mcpserver/start-connect.ts +1 -1
- package/src/domain/platform/__tests__/platform.test.ts +47 -1
- package/src/domain/platform/controller.ts +33 -14
- package/src/domain/plugin/__tests__/materialize.test.ts +60 -1
- package/src/domain/plugin/__tests__/plugin.test.ts +122 -1
- package/src/domain/plugin/__tests__/sanitize.test.ts +2 -2
- package/src/domain/plugin/materialize/agent.ts +38 -0
- package/src/domain/plugin/materialize/plan.ts +7 -1
- package/src/domain/plugin/overlay/sanitize.ts +4 -2
- package/src/domain/project/__tests__/reconcile.test.ts +51 -5
- package/src/domain/project/reconcile.ts +20 -15
- package/src/domain/workflowexecution/__tests__/phases.test.ts +58 -0
- package/src/domain/workflowexecution/phases.ts +33 -0
- package/src/extensions/__tests__/extension-composition.test.ts +7 -1
- package/src/identity/__tests__/oidc-verifier.test.ts +22 -0
- package/src/identity/oidc-verifier.ts +8 -1
- package/src/pipeline/__tests__/kind-by-id-prefix.test.ts +75 -0
- package/src/pipeline/apiresource-meta.ts +41 -0
- package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +55 -35
- package/src/pipeline/steps/guard-memory-capture.ts +5 -3
- package/src/runnerauth/__tests__/bound-execution.test.ts +334 -0
- package/src/runnerauth/__tests__/built-in-runner-credential-provider.test.ts +516 -0
- package/src/runnerauth/__tests__/dispatch-credential.test.ts +94 -0
- package/src/runnerauth/__tests__/runner-credential-provider.test.ts +27 -0
- package/src/runnerauth/__tests__/runner-subject-composed.test.ts +497 -0
- package/src/runnerauth/__tests__/runner-subject-verifier.test.ts +541 -0
- package/src/runnerauth/bound-execution.ts +270 -0
- package/src/runnerauth/built-in-runner-credential-provider.ts +300 -0
- package/src/runnerauth/constants.ts +74 -0
- package/src/runnerauth/dispatch-credential.ts +63 -0
- package/src/runnerauth/runner-credential-provider.ts +60 -11
- package/src/runnerauth/runner-subject-verifier.ts +152 -0
- package/src/runnerauth/runnerauth.ts +148 -38
- package/src/temporal/agentexecution/__tests__/engine-client.test.ts +107 -38
- package/src/temporal/agentexecution/__tests__/invoke-workflow.test.ts +105 -2
- package/src/temporal/agentexecution/engine-client.ts +27 -2
- package/src/temporal/agentexecution/workflow-input.ts +13 -0
- package/src/temporal/agentexecution/workflows/invoke-agent-execution.ts +90 -44
- package/src/temporal/workflowexecution/__tests__/engine-client.test.ts +139 -33
- package/src/temporal/workflowexecution/engine-client.ts +48 -21
- package/src/temporal/workflowexecution/workflow-input.ts +14 -0
|
@@ -157,28 +157,77 @@ const approvalGateResolvedSignal = defineSignal(SIGNAL_APPROVAL_GATE_RESOLVED);
|
|
|
157
157
|
|
|
158
158
|
// ─── Activity proxies (options are contract; see the census table) ──────
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* The typed input of ExecuteDeepAgent and ExecuteCursor — the runner's
|
|
162
|
+
* ExecuteActivityInput (shared/activity-input.ts), snake_case keys, a
|
|
163
|
+
* cross-component wire contract. `execution_context_token` is the run
|
|
164
|
+
* credential the workflow input carries (workflow-input.ts), handed on
|
|
165
|
+
* so the activity can present it on the run's own RPCs; omitted when the
|
|
166
|
+
* dispatch carried none (the omitempty convention). Adding the key needed
|
|
167
|
+
* no patch marker: an in-flight history's input has no token, so the
|
|
168
|
+
* commands its replay generates are byte-for-byte what they were.
|
|
169
|
+
*/
|
|
170
|
+
interface ExecuteActivityInput {
|
|
171
|
+
readonly execution_id: string;
|
|
172
|
+
readonly thread_id: string;
|
|
173
|
+
readonly invoker_identity_account_id: string;
|
|
174
|
+
readonly turn_seq: number;
|
|
175
|
+
readonly execution_context_token?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The typed input of GenerateSessionSubject — the runner's
|
|
180
|
+
* GenerateSessionSubjectInput (activities/generate-session-subject.ts),
|
|
181
|
+
* snake_case keys. Sent ONLY when the dispatch carries a run credential;
|
|
182
|
+
* otherwise the activity is called with the positional execution id it has
|
|
183
|
+
* always received, byte for byte, because a server whose dispatches carry
|
|
184
|
+
* no credential (the cloud's: its runner credential is provisioned, not
|
|
185
|
+
* dispatched) talks to desktop runners in the field that predate the object
|
|
186
|
+
* shape, and a session title is not worth breaking for them. A runner that
|
|
187
|
+
* knows the object accepts both.
|
|
188
|
+
*/
|
|
189
|
+
interface GenerateSessionSubjectInput {
|
|
190
|
+
readonly execution_id: string;
|
|
191
|
+
readonly execution_context_token: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
160
194
|
interface RunnerActivities {
|
|
161
195
|
[ENSURE_THREAD_ACTIVITY_NAME]: (
|
|
162
196
|
sessionId: string,
|
|
163
197
|
agentId: string,
|
|
164
198
|
) => Promise<string>;
|
|
165
|
-
[EXECUTE_DEEP_AGENT_ACTIVITY_NAME]: (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
[EXECUTE_CURSOR_ACTIVITY_NAME]: (input: {
|
|
172
|
-
execution_id: string;
|
|
173
|
-
thread_id: string;
|
|
174
|
-
invoker_identity_account_id: string;
|
|
175
|
-
turn_seq: number;
|
|
176
|
-
}) => Promise<RunnerActivityResult | null>;
|
|
199
|
+
[EXECUTE_DEEP_AGENT_ACTIVITY_NAME]: (
|
|
200
|
+
input: ExecuteActivityInput,
|
|
201
|
+
) => Promise<RunnerActivityResult | null>;
|
|
202
|
+
[EXECUTE_CURSOR_ACTIVITY_NAME]: (
|
|
203
|
+
input: ExecuteActivityInput,
|
|
204
|
+
) => Promise<RunnerActivityResult | null>;
|
|
177
205
|
[GENERATE_SESSION_SUBJECT_ACTIVITY_NAME]: (
|
|
178
|
-
|
|
206
|
+
input: GenerateSessionSubjectInput | string,
|
|
179
207
|
) => Promise<void>;
|
|
180
208
|
}
|
|
181
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The ONE builder of an Execute* input for both harness flows and both
|
|
212
|
+
* invocations (first turn and every HITL re-invocation): the run's
|
|
213
|
+
* coordinates from the workflow input, the harness thread and the turn
|
|
214
|
+
* from the caller.
|
|
215
|
+
*/
|
|
216
|
+
function executeActivityInput(
|
|
217
|
+
input: InvokeAgentExecutionWorkflowInput,
|
|
218
|
+
threadId: string,
|
|
219
|
+
turnSeq: number,
|
|
220
|
+
): ExecuteActivityInput {
|
|
221
|
+
const runCredential = input.execution_context_token ?? "";
|
|
222
|
+
return {
|
|
223
|
+
execution_id: input.execution_id,
|
|
224
|
+
thread_id: threadId,
|
|
225
|
+
invoker_identity_account_id: input.invoker_identity_account_id ?? "",
|
|
226
|
+
turn_seq: turnSeq,
|
|
227
|
+
...(runCredential !== "" ? { execution_context_token: runCredential } : {}),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
182
231
|
function newEnsureThreadProxy(taskQueue: string) {
|
|
183
232
|
return proxyActivities<Pick<RunnerActivities, typeof ENSURE_THREAD_ACTIVITY_NAME>>({
|
|
184
233
|
taskQueue,
|
|
@@ -516,7 +565,6 @@ async function executeDeepAgentFlow(
|
|
|
516
565
|
): Promise<RunnerActivityResult> {
|
|
517
566
|
const executionId = input.execution_id;
|
|
518
567
|
const sessionId = input.session_id;
|
|
519
|
-
const invoker = input.invoker_identity_account_id ?? "";
|
|
520
568
|
|
|
521
569
|
log.info("Step 1: Ensuring thread", { sessionId, agentId: input.agent_id });
|
|
522
570
|
const ensureThread = newEnsureThreadProxy(activityTaskQueue);
|
|
@@ -531,7 +579,7 @@ async function executeDeepAgentFlow(
|
|
|
531
579
|
}
|
|
532
580
|
log.info("Thread ensured", { threadId });
|
|
533
581
|
|
|
534
|
-
fireGenerateSessionSubject(activityTaskQueue,
|
|
582
|
+
fireGenerateSessionSubject(activityTaskQueue, input);
|
|
535
583
|
|
|
536
584
|
log.info("Step 2: Executing deep agent", { executionId, threadId });
|
|
537
585
|
const agentProxy = newExecuteAgentProxy(activityTaskQueue);
|
|
@@ -547,20 +595,14 @@ async function executeDeepAgentFlow(
|
|
|
547
595
|
signals,
|
|
548
596
|
parentWorkflowId: input.parent_workflow_id ?? "",
|
|
549
597
|
firstInvoke: () =>
|
|
550
|
-
agentProxy[EXECUTE_DEEP_AGENT_ACTIVITY_NAME](
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
invoker_identity_account_id: invoker,
|
|
554
|
-
turn_seq: 0,
|
|
555
|
-
}),
|
|
598
|
+
agentProxy[EXECUTE_DEEP_AGENT_ACTIVITY_NAME](
|
|
599
|
+
executeActivityInput(input, threadId, 0),
|
|
600
|
+
),
|
|
556
601
|
// The LangGraph thread id is stable — re-invocations reuse it.
|
|
557
602
|
reinvoke: (turnSeq) =>
|
|
558
|
-
agentProxy[EXECUTE_DEEP_AGENT_ACTIVITY_NAME](
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
invoker_identity_account_id: invoker,
|
|
562
|
-
turn_seq: turnSeq,
|
|
563
|
-
}),
|
|
603
|
+
agentProxy[EXECUTE_DEEP_AGENT_ACTIVITY_NAME](
|
|
604
|
+
executeActivityInput(input, threadId, turnSeq),
|
|
605
|
+
),
|
|
564
606
|
nullResultMessage: "activity returned null status - this should never happen",
|
|
565
607
|
nullResultAfterApprovalMessage:
|
|
566
608
|
"activity returned null status after approval - this should never happen",
|
|
@@ -584,9 +626,8 @@ async function executeCursorFlow(
|
|
|
584
626
|
): Promise<RunnerActivityResult> {
|
|
585
627
|
const executionId = input.execution_id;
|
|
586
628
|
const sessionId = input.session_id;
|
|
587
|
-
const invoker = input.invoker_identity_account_id ?? "";
|
|
588
629
|
|
|
589
|
-
fireGenerateSessionSubject(activityTaskQueue,
|
|
630
|
+
fireGenerateSessionSubject(activityTaskQueue, input);
|
|
590
631
|
|
|
591
632
|
log.info("Executing Cursor agent", { executionId, sessionId });
|
|
592
633
|
const agentProxy = newExecuteAgentProxy(activityTaskQueue);
|
|
@@ -612,12 +653,9 @@ async function executeCursorFlow(
|
|
|
612
653
|
);
|
|
613
654
|
harnessStateId = "";
|
|
614
655
|
}
|
|
615
|
-
return agentProxy[EXECUTE_CURSOR_ACTIVITY_NAME](
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
invoker_identity_account_id: invoker,
|
|
619
|
-
turn_seq: 0,
|
|
620
|
-
});
|
|
656
|
+
return agentProxy[EXECUTE_CURSOR_ACTIVITY_NAME](
|
|
657
|
+
executeActivityInput(input, harnessStateId, 0),
|
|
658
|
+
);
|
|
621
659
|
},
|
|
622
660
|
reinvoke: async (turnSeq) => {
|
|
623
661
|
let harnessStateId: string;
|
|
@@ -634,12 +672,9 @@ async function executeCursorFlow(
|
|
|
634
672
|
cycle: turnSeq,
|
|
635
673
|
harnessStateId,
|
|
636
674
|
});
|
|
637
|
-
return agentProxy[EXECUTE_CURSOR_ACTIVITY_NAME](
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
invoker_identity_account_id: invoker,
|
|
641
|
-
turn_seq: turnSeq,
|
|
642
|
-
});
|
|
675
|
+
return agentProxy[EXECUTE_CURSOR_ACTIVITY_NAME](
|
|
676
|
+
executeActivityInput(input, harnessStateId, turnSeq),
|
|
677
|
+
);
|
|
643
678
|
},
|
|
644
679
|
nullResultMessage:
|
|
645
680
|
"cursor activity returned null status - this should never happen",
|
|
@@ -649,15 +684,26 @@ async function executeCursorFlow(
|
|
|
649
684
|
});
|
|
650
685
|
}
|
|
651
686
|
|
|
652
|
-
/**
|
|
687
|
+
/**
|
|
688
|
+
* GenerateSessionSubject: fire-and-forget, non-blocking (issue #665). The
|
|
689
|
+
* argument shape follows the run credential's presence — see
|
|
690
|
+
* GenerateSessionSubjectInput for why the credential-less dispatch keeps
|
|
691
|
+
* the positional string.
|
|
692
|
+
*/
|
|
653
693
|
function fireGenerateSessionSubject(
|
|
654
694
|
activityTaskQueue: string,
|
|
655
|
-
|
|
695
|
+
input: InvokeAgentExecutionWorkflowInput,
|
|
656
696
|
): void {
|
|
697
|
+
const executionId = input.execution_id;
|
|
698
|
+
const runCredential = input.execution_context_token ?? "";
|
|
699
|
+
const argument: GenerateSessionSubjectInput | string =
|
|
700
|
+
runCredential !== ""
|
|
701
|
+
? { execution_id: executionId, execution_context_token: runCredential }
|
|
702
|
+
: executionId;
|
|
657
703
|
const subjectProxy = newSubjectProxy(activityTaskQueue);
|
|
658
704
|
void (async () => {
|
|
659
705
|
try {
|
|
660
|
-
await subjectProxy[GENERATE_SESSION_SUBJECT_ACTIVITY_NAME](
|
|
706
|
+
await subjectProxy[GENERATE_SESSION_SUBJECT_ACTIVITY_NAME](argument);
|
|
661
707
|
} catch (error) {
|
|
662
708
|
log.warn("Session subject generation failed (non-critical)", {
|
|
663
709
|
executionId,
|
|
@@ -6,9 +6,18 @@
|
|
|
6
6
|
* raw-signal convention, the WorkflowNotFoundError → seam-sentinel
|
|
7
7
|
* mapping, and the provider's memoization/disconnected semantics.
|
|
8
8
|
*
|
|
9
|
+
* And the run credential on the input (2026-09-16): minted through the
|
|
10
|
+
* composed provider's capability on BOTH dispatch lanes (start and
|
|
11
|
+
* signalWithStart), bound to the execution with no clock; the key is
|
|
12
|
+
* ABSENT when the provider lacks the capability (an edition credentialed
|
|
13
|
+
* another way), when it is keyless, and when the mint throws — a dispatch
|
|
14
|
+
* never fails on a mint.
|
|
15
|
+
*
|
|
9
16
|
* The Temporal Client is a recording double — the real dial path is
|
|
10
17
|
* proven by local-execution.
|
|
11
18
|
*/
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
|
|
12
21
|
import { describe, expect, it } from "vitest";
|
|
13
22
|
|
|
14
23
|
import type { Client } from "@temporalio/client";
|
|
@@ -27,11 +36,18 @@ import {
|
|
|
27
36
|
WORKFLOW_ROUTING_GLOBAL,
|
|
28
37
|
WorkflowExecutionTemporalConfig,
|
|
29
38
|
} from "../../../domain/workflowexecution/temporal/config.js";
|
|
39
|
+
import type { RunCredentialMint } from "../../../runnerauth/dispatch-credential.js";
|
|
40
|
+
import { newExecutionScopedRunnerCredentialProvider } from "../../../runnerauth/runner-credential-provider.js";
|
|
41
|
+
import {
|
|
42
|
+
isClockedToken,
|
|
43
|
+
RunnerAuthService,
|
|
44
|
+
} from "../../../runnerauth/runnerauth.js";
|
|
30
45
|
import type { TemporalManager } from "../../manager.js";
|
|
31
46
|
import {
|
|
32
47
|
newWorkflowExecutionEngineStateProvider,
|
|
33
48
|
TemporalWorkflowExecutionEngine,
|
|
34
49
|
} from "../engine-client.js";
|
|
50
|
+
import type { TemporalWorkflowExecutionEngineDeps } from "../engine-client.js";
|
|
35
51
|
|
|
36
52
|
const silentLogger = createLogger({
|
|
37
53
|
level: "error",
|
|
@@ -46,6 +62,27 @@ const config = new WorkflowExecutionTemporalConfig(
|
|
|
46
62
|
WORKFLOW_DEFAULT_EXECUTION_TARGET_LOCAL,
|
|
47
63
|
);
|
|
48
64
|
|
|
65
|
+
/** The keyed OSS default — what a real open-source boot composes. */
|
|
66
|
+
const runnerAuth = RunnerAuthService.create(randomBytes(32));
|
|
67
|
+
const keyedCredentials = newExecutionScopedRunnerCredentialProvider(runnerAuth);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* One construction site for every arm: the realistic deps by default,
|
|
71
|
+
* any of them overridable, so the next dependency costs one line here.
|
|
72
|
+
*/
|
|
73
|
+
function newEngine(
|
|
74
|
+
client: Client,
|
|
75
|
+
overrides: Partial<Omit<TemporalWorkflowExecutionEngineDeps, "client">> = {},
|
|
76
|
+
): TemporalWorkflowExecutionEngine {
|
|
77
|
+
return new TemporalWorkflowExecutionEngine({
|
|
78
|
+
client,
|
|
79
|
+
config,
|
|
80
|
+
runnerCredentials: keyedCredentials,
|
|
81
|
+
logger: silentLogger,
|
|
82
|
+
...overrides,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
49
86
|
interface RecordedStart {
|
|
50
87
|
readonly workflowType: string;
|
|
51
88
|
readonly options: Record<string, unknown>;
|
|
@@ -93,6 +130,15 @@ function newClientDouble(handleError?: Error) {
|
|
|
93
130
|
return { client, starts, signalWithStarts, handleCalls };
|
|
94
131
|
}
|
|
95
132
|
|
|
133
|
+
/** The one workflow input a recorded start (or signalWithStart) carried. */
|
|
134
|
+
function inputOf(recorded: RecordedStart[]): Record<string, unknown> {
|
|
135
|
+
expect(recorded).toHaveLength(1);
|
|
136
|
+
return (recorded[0]!.options["args"] as unknown[])[0] as Record<
|
|
137
|
+
string,
|
|
138
|
+
unknown
|
|
139
|
+
>;
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
function startInput(
|
|
97
143
|
overrides: Partial<StartWorkflowExecutionInput> = {},
|
|
98
144
|
): StartWorkflowExecutionInput {
|
|
@@ -110,11 +156,7 @@ function startInput(
|
|
|
110
156
|
describe("TemporalWorkflowExecutionEngine", () => {
|
|
111
157
|
it("starts the orchestrator with the pinned ID, queue, memo, and omitempty input", async () => {
|
|
112
158
|
const double = newClientDouble();
|
|
113
|
-
const engine =
|
|
114
|
-
client: double.client,
|
|
115
|
-
config,
|
|
116
|
-
logger: silentLogger,
|
|
117
|
-
});
|
|
159
|
+
const engine = newEngine(double.client);
|
|
118
160
|
|
|
119
161
|
await engine.startInvokeWorkflow(startInput());
|
|
120
162
|
|
|
@@ -125,7 +167,9 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
125
167
|
"stigmer/workflow-execution/invoke/wfe-1",
|
|
126
168
|
);
|
|
127
169
|
expect(start.options["taskQueue"]).toBe("workflow_execution_stigmer");
|
|
128
|
-
expect(start.options["memo"]).toEqual({
|
|
170
|
+
expect(start.options["memo"]).toEqual({
|
|
171
|
+
runnerTaskQueue: "stigmer_runner",
|
|
172
|
+
});
|
|
129
173
|
// Go's omitempty shape: recovery_mode false is OMITTED, not false.
|
|
130
174
|
expect(start.options["args"]).toEqual([
|
|
131
175
|
{
|
|
@@ -133,6 +177,8 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
133
177
|
workflow_instance_id: "wfi-1",
|
|
134
178
|
workflow_id: "wf-1",
|
|
135
179
|
org_id: "org-1",
|
|
180
|
+
// The run credential, minted per dispatch (its own arms below).
|
|
181
|
+
execution_context_token: expect.any(String),
|
|
136
182
|
},
|
|
137
183
|
]);
|
|
138
184
|
// No workflowRunTimeout — durable execution (module header).
|
|
@@ -141,11 +187,7 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
141
187
|
|
|
142
188
|
it("omits every zero-valued optional field and carries recovery_mode when set", async () => {
|
|
143
189
|
const double = newClientDouble();
|
|
144
|
-
const engine =
|
|
145
|
-
client: double.client,
|
|
146
|
-
config,
|
|
147
|
-
logger: silentLogger,
|
|
148
|
-
});
|
|
190
|
+
const engine = newEngine(double.client);
|
|
149
191
|
|
|
150
192
|
await engine.startInvokeWorkflow(
|
|
151
193
|
startInput({
|
|
@@ -157,18 +199,89 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
157
199
|
);
|
|
158
200
|
|
|
159
201
|
expect(double.starts[0]!.options["args"]).toEqual([
|
|
160
|
-
{
|
|
202
|
+
{
|
|
203
|
+
execution_id: "wfe-1",
|
|
204
|
+
recovery_mode: true,
|
|
205
|
+
execution_context_token: expect.any(String),
|
|
206
|
+
},
|
|
161
207
|
]);
|
|
162
208
|
});
|
|
163
209
|
|
|
164
|
-
it("
|
|
210
|
+
it("carries the run credential minted through the provider: bound to the execution, no clock", async () => {
|
|
165
211
|
const double = newClientDouble();
|
|
166
|
-
const engine =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
const engine = newEngine(double.client);
|
|
213
|
+
|
|
214
|
+
await engine.startInvokeWorkflow(startInput({ executionId: "wex_cred" }));
|
|
215
|
+
|
|
216
|
+
const token = inputOf(double.starts)["execution_context_token"];
|
|
217
|
+
expect(typeof token).toBe("string");
|
|
218
|
+
expect(runnerAuth.verify(token as string)).toBe("wex_cred");
|
|
219
|
+
expect(isClockedToken(token as string)).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("signalWithStart mints the same credential — the lane that may be the start", async () => {
|
|
223
|
+
const double = newClientDouble();
|
|
224
|
+
const engine = newEngine(double.client);
|
|
225
|
+
|
|
226
|
+
await engine.signalWithStart(
|
|
227
|
+
startInput({ executionId: "wex_sws" }),
|
|
228
|
+
"relaySignal",
|
|
229
|
+
{
|
|
230
|
+
signalName: "human_input_step1",
|
|
231
|
+
payload: { answer: 42 },
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const token = inputOf(double.signalWithStarts)["execution_context_token"];
|
|
236
|
+
expect(runnerAuth.verify(token as string)).toBe("wex_sws");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("omits the credential when the provider lacks the capability — an edition credentialed another way", async () => {
|
|
240
|
+
const double = newClientDouble();
|
|
241
|
+
const withoutCapability: RunCredentialMint = {};
|
|
242
|
+
const engine = newEngine(double.client, {
|
|
243
|
+
runnerCredentials: withoutCapability,
|
|
170
244
|
});
|
|
171
245
|
|
|
246
|
+
await engine.startInvokeWorkflow(startInput());
|
|
247
|
+
await engine.signalWithStart(startInput(), "relaySignal", null);
|
|
248
|
+
|
|
249
|
+
expect("execution_context_token" in inputOf(double.starts)).toBe(false);
|
|
250
|
+
expect("execution_context_token" in inputOf(double.signalWithStarts)).toBe(
|
|
251
|
+
false,
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("omits the credential when the provider is keyless", async () => {
|
|
256
|
+
const double = newClientDouble();
|
|
257
|
+
const keyless = newExecutionScopedRunnerCredentialProvider(
|
|
258
|
+
RunnerAuthService.create(undefined),
|
|
259
|
+
);
|
|
260
|
+
const engine = newEngine(double.client, { runnerCredentials: keyless });
|
|
261
|
+
|
|
262
|
+
await engine.startInvokeWorkflow(startInput());
|
|
263
|
+
|
|
264
|
+
expect("execution_context_token" in inputOf(double.starts)).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("a mint that throws degrades to a dispatch without the credential — the start still happens", async () => {
|
|
268
|
+
const double = newClientDouble();
|
|
269
|
+
const throwing: RunCredentialMint = {
|
|
270
|
+
mintRunCredential(): string {
|
|
271
|
+
throw new Error("signing failed");
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
const engine = newEngine(double.client, { runnerCredentials: throwing });
|
|
275
|
+
|
|
276
|
+
await engine.startInvokeWorkflow(startInput());
|
|
277
|
+
|
|
278
|
+
expect("execution_context_token" in inputOf(double.starts)).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("signalWithStart carries the signal, its payload, and the same start options", async () => {
|
|
282
|
+
const double = newClientDouble();
|
|
283
|
+
const engine = newEngine(double.client);
|
|
284
|
+
|
|
172
285
|
await engine.signalWithStart(startInput(), "relaySignal", {
|
|
173
286
|
signalName: "human_input_step1",
|
|
174
287
|
payload: { answer: 42 },
|
|
@@ -189,17 +302,17 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
189
302
|
|
|
190
303
|
it("raw signal sends exactly one argument, null when the payload is absent", async () => {
|
|
191
304
|
const double = newClientDouble();
|
|
192
|
-
const engine =
|
|
193
|
-
client: double.client,
|
|
194
|
-
config,
|
|
195
|
-
logger: silentLogger,
|
|
196
|
-
});
|
|
305
|
+
const engine = newEngine(double.client);
|
|
197
306
|
|
|
198
307
|
await engine.signalWorkflow("wf-id-1", "pause", "user requested");
|
|
199
308
|
await engine.signalWorkflow("wf-id-1", "resume", undefined);
|
|
200
309
|
|
|
201
310
|
expect(double.handleCalls).toEqual([
|
|
202
|
-
{
|
|
311
|
+
{
|
|
312
|
+
workflowId: "wf-id-1",
|
|
313
|
+
method: "signal",
|
|
314
|
+
args: ["pause", "user requested"],
|
|
315
|
+
},
|
|
203
316
|
// Go SignalWorkflow(nil) — one nil argument, not zero arguments.
|
|
204
317
|
{ workflowId: "wf-id-1", method: "signal", args: ["resume", null] },
|
|
205
318
|
]);
|
|
@@ -208,11 +321,7 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
208
321
|
it("maps WorkflowNotFoundError onto the seam's sentinel for signal/cancel/terminate", async () => {
|
|
209
322
|
const notFound = new WorkflowNotFoundError("gone", "wf-id-x", undefined);
|
|
210
323
|
const double = newClientDouble(notFound);
|
|
211
|
-
const engine =
|
|
212
|
-
client: double.client,
|
|
213
|
-
config,
|
|
214
|
-
logger: silentLogger,
|
|
215
|
-
});
|
|
324
|
+
const engine = newEngine(double.client);
|
|
216
325
|
|
|
217
326
|
await expect(
|
|
218
327
|
engine.signalWorkflow("wf-id-x", "pause", "r"),
|
|
@@ -228,11 +337,7 @@ describe("TemporalWorkflowExecutionEngine", () => {
|
|
|
228
337
|
it("passes non-not-found errors through unchanged", async () => {
|
|
229
338
|
const boom = new Error("connection refused");
|
|
230
339
|
const double = newClientDouble(boom);
|
|
231
|
-
const engine =
|
|
232
|
-
client: double.client,
|
|
233
|
-
config,
|
|
234
|
-
logger: silentLogger,
|
|
235
|
-
});
|
|
340
|
+
const engine = newEngine(double.client);
|
|
236
341
|
|
|
237
342
|
await expect(engine.cancelWorkflow("wf-id-x")).rejects.toBe(boom);
|
|
238
343
|
});
|
|
@@ -248,6 +353,7 @@ describe("newWorkflowExecutionEngineStateProvider", () => {
|
|
|
248
353
|
const provider = newWorkflowExecutionEngineStateProvider({
|
|
249
354
|
manager,
|
|
250
355
|
config,
|
|
356
|
+
runnerCredentials: keyedCredentials,
|
|
251
357
|
logger: silentLogger,
|
|
252
358
|
});
|
|
253
359
|
|
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
* signal/cancel/terminate operations take a workflowId verbatim. That
|
|
27
27
|
* asymmetry is the seam's documented contract.
|
|
28
28
|
*
|
|
29
|
+
* The run credential rides the input (2026-09-16): every dispatch — a
|
|
30
|
+
* fresh start, a recovery, and both signalWithStart lanes (sendSignal,
|
|
31
|
+
* submitWorkflowTaskApproval), which may be the start when they race the
|
|
32
|
+
* create — asks the composed credential provider for the run's credential
|
|
33
|
+
* (runnerauth/dispatch-credential.ts) and carries it as
|
|
34
|
+
* `execution_context_token`, the connect lane's shape. signalWithStart
|
|
35
|
+
* mints even when the workflow already exists and Temporal discards the
|
|
36
|
+
* input: the mint is a local HMAC with no read, and one input builder
|
|
37
|
+
* serving every lane is worth more than the spared signature. A provider
|
|
38
|
+
* without the capability (an edition credentialed another way) leaves the
|
|
39
|
+
* key off.
|
|
40
|
+
*
|
|
29
41
|
* Proven by the workflowexecution suites on local-execution.
|
|
30
42
|
*/
|
|
31
43
|
import type { JsonValue } from "@bufbuild/protobuf";
|
|
@@ -42,6 +54,8 @@ import {
|
|
|
42
54
|
type WorkflowExecutionEngineStateProvider,
|
|
43
55
|
} from "../../domain/workflowexecution/engine.js";
|
|
44
56
|
import type { WorkflowExecutionTemporalConfig } from "../../domain/workflowexecution/temporal/config.js";
|
|
57
|
+
import { runCredentialForDispatch } from "../../runnerauth/dispatch-credential.js";
|
|
58
|
+
import type { RunCredentialMint } from "../../runnerauth/dispatch-credential.js";
|
|
45
59
|
import type { TemporalManager } from "../manager.js";
|
|
46
60
|
import { resolveWorkflowTaskQueue } from "./dispatch.js";
|
|
47
61
|
import {
|
|
@@ -54,6 +68,8 @@ import type { InvokeWorkflowExecutionWorkflowInput } from "./workflow-input.js";
|
|
|
54
68
|
export interface TemporalWorkflowExecutionEngineDeps {
|
|
55
69
|
readonly client: Client;
|
|
56
70
|
readonly config: WorkflowExecutionTemporalConfig;
|
|
71
|
+
/** The composed credential provider — the dispatch reads its run-credential capability (module header). */
|
|
72
|
+
readonly runnerCredentials: RunCredentialMint;
|
|
57
73
|
readonly logger: Logger;
|
|
58
74
|
}
|
|
59
75
|
|
|
@@ -82,7 +98,7 @@ export class TemporalWorkflowExecutionEngine
|
|
|
82
98
|
workflowId,
|
|
83
99
|
taskQueue: config.stigmerQueue,
|
|
84
100
|
memo: { [MEMO_RUNNER_TASK_QUEUE]: dispatch.taskQueue },
|
|
85
|
-
args: [buildWorkflowInput(input)],
|
|
101
|
+
args: [this.buildWorkflowInput(input)],
|
|
86
102
|
});
|
|
87
103
|
|
|
88
104
|
logger.info("Started InvokeWorkflowExecutionWorkflow", {
|
|
@@ -117,7 +133,7 @@ export class TemporalWorkflowExecutionEngine
|
|
|
117
133
|
workflowId,
|
|
118
134
|
taskQueue: config.stigmerQueue,
|
|
119
135
|
memo: { [MEMO_RUNNER_TASK_QUEUE]: dispatch.taskQueue },
|
|
120
|
-
args: [buildWorkflowInput(input)],
|
|
136
|
+
args: [this.buildWorkflowInput(input)],
|
|
121
137
|
signal: signalName,
|
|
122
138
|
signalArgs: [payload],
|
|
123
139
|
},
|
|
@@ -164,26 +180,35 @@ export class TemporalWorkflowExecutionEngine
|
|
|
164
180
|
throw mapNotFound(error, workflowId);
|
|
165
181
|
}
|
|
166
182
|
}
|
|
167
|
-
}
|
|
168
183
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
/**
|
|
185
|
+
* The slim input with Go's omitempty shape (workflow-input.ts): zero
|
|
186
|
+
* values are omitted so TS-authored histories carry the same keys a
|
|
187
|
+
* Go-authored one would. The cloud-only fields are unmodeled (the
|
|
188
|
+
* seam's ratified boundary). The run credential is the one key minted
|
|
189
|
+
* here rather than copied from the caller (module header).
|
|
190
|
+
*/
|
|
191
|
+
private buildWorkflowInput(
|
|
192
|
+
input: StartWorkflowExecutionInput,
|
|
193
|
+
): InvokeWorkflowExecutionWorkflowInput {
|
|
194
|
+
const runCredential = runCredentialForDispatch(
|
|
195
|
+
this.deps.runnerCredentials,
|
|
196
|
+
input.executionId,
|
|
197
|
+
this.deps.logger,
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
execution_id: input.executionId,
|
|
201
|
+
...(input.workflowInstanceId !== ""
|
|
202
|
+
? { workflow_instance_id: input.workflowInstanceId }
|
|
203
|
+
: {}),
|
|
204
|
+
...(input.workflowId !== "" ? { workflow_id: input.workflowId } : {}),
|
|
205
|
+
...(input.orgId !== "" ? { org_id: input.orgId } : {}),
|
|
206
|
+
...(input.recoveryMode ? { recovery_mode: true } : {}),
|
|
207
|
+
...(runCredential !== ""
|
|
208
|
+
? { execution_context_token: runCredential }
|
|
209
|
+
: {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
187
212
|
}
|
|
188
213
|
|
|
189
214
|
/**
|
|
@@ -200,6 +225,7 @@ function mapNotFound(error: unknown, workflowId: string): unknown {
|
|
|
200
225
|
export interface WorkflowExecutionEngineStateProviderDeps {
|
|
201
226
|
readonly manager: TemporalManager;
|
|
202
227
|
readonly config: WorkflowExecutionTemporalConfig;
|
|
228
|
+
readonly runnerCredentials: RunCredentialMint;
|
|
203
229
|
readonly logger: Logger;
|
|
204
230
|
}
|
|
205
231
|
|
|
@@ -232,6 +258,7 @@ export function newWorkflowExecutionEngineStateProvider(
|
|
|
232
258
|
engine: new TemporalWorkflowExecutionEngine({
|
|
233
259
|
client,
|
|
234
260
|
config: deps.config,
|
|
261
|
+
runnerCredentials: deps.runnerCredentials,
|
|
235
262
|
logger: deps.logger,
|
|
236
263
|
}),
|
|
237
264
|
},
|
|
@@ -28,6 +28,20 @@ export interface InvokeWorkflowExecutionWorkflowInput {
|
|
|
28
28
|
* recover pipeline; normal executions omit it (Go omitempty).
|
|
29
29
|
*/
|
|
30
30
|
readonly recovery_mode?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The run credential (runnerauth: a clockless execution-scoped token
|
|
33
|
+
* bound to this execution), minted by the engine client at dispatch
|
|
34
|
+
* when the composed credential provider defines `mintRunCredential`
|
|
35
|
+
* and omitted otherwise. The connect lane's key for the same token
|
|
36
|
+
* type (domain/mcpserver/engine.ts). The orchestrator passes this
|
|
37
|
+
* object whole to the runner's child workflow, so the runner reads it
|
|
38
|
+
* from here and presents it on the run's own RPCs: under the built-in
|
|
39
|
+
* authorization posture it admits the runner as the human whose run
|
|
40
|
+
* this is, for as long as the run lives. In Temporal history in the
|
|
41
|
+
* clear, like the connect lane's — history is operator-only, and the
|
|
42
|
+
* operator holds the signing key already.
|
|
43
|
+
*/
|
|
44
|
+
readonly execution_context_token?: string;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
/**
|