@stigmer/server 3.37.0 → 3.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/domain/agentexecution/artifacts.d.ts.map +1 -1
  2. package/dist/domain/agentexecution/artifacts.js +15 -6
  3. package/dist/domain/agentexecution/artifacts.js.map +1 -1
  4. package/dist/domain/agentexecution/lifecycle.d.ts.map +1 -1
  5. package/dist/domain/agentexecution/lifecycle.js +5 -3
  6. package/dist/domain/agentexecution/lifecycle.js.map +1 -1
  7. package/dist/domain/agentexecution/platform-rows.d.ts +63 -0
  8. package/dist/domain/agentexecution/platform-rows.d.ts.map +1 -0
  9. package/dist/domain/agentexecution/platform-rows.js +106 -0
  10. package/dist/domain/agentexecution/platform-rows.js.map +1 -0
  11. package/dist/domain/agentexecution/subscribe.d.ts.map +1 -1
  12. package/dist/domain/agentexecution/subscribe.js +7 -4
  13. package/dist/domain/agentexecution/subscribe.js.map +1 -1
  14. package/dist/domain/agentexecution/update-status.d.ts.map +1 -1
  15. package/dist/domain/agentexecution/update-status.js +48 -17
  16. package/dist/domain/agentexecution/update-status.js.map +1 -1
  17. package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
  18. package/dist/domain/workflowexecution/create-steps.js +6 -2
  19. package/dist/domain/workflowexecution/create-steps.js.map +1 -1
  20. package/dist/domain/workflowexecution/lifecycle.d.ts.map +1 -1
  21. package/dist/domain/workflowexecution/lifecycle.js +5 -2
  22. package/dist/domain/workflowexecution/lifecycle.js.map +1 -1
  23. package/dist/domain/workflowexecution/send-signal.d.ts.map +1 -1
  24. package/dist/domain/workflowexecution/send-signal.js +6 -3
  25. package/dist/domain/workflowexecution/send-signal.js.map +1 -1
  26. package/dist/domain/workflowexecution/submit-approval.d.ts.map +1 -1
  27. package/dist/domain/workflowexecution/submit-approval.js +6 -2
  28. package/dist/domain/workflowexecution/submit-approval.js.map +1 -1
  29. package/dist/domain/workflowexecution/submit-file-decision.d.ts.map +1 -1
  30. package/dist/domain/workflowexecution/submit-file-decision.js +6 -2
  31. package/dist/domain/workflowexecution/submit-file-decision.js.map +1 -1
  32. package/dist/domain/workflowexecution/submit-workflow-task-approval.d.ts.map +1 -1
  33. package/dist/domain/workflowexecution/submit-workflow-task-approval.js +6 -2
  34. package/dist/domain/workflowexecution/submit-workflow-task-approval.js.map +1 -1
  35. package/dist/domain/workflowexecution/subscribe-events.d.ts.map +1 -1
  36. package/dist/domain/workflowexecution/subscribe-events.js +6 -2
  37. package/dist/domain/workflowexecution/subscribe-events.js.map +1 -1
  38. package/dist/domain/workflowexecution/subscribe.d.ts.map +1 -1
  39. package/dist/domain/workflowexecution/subscribe.js +7 -4
  40. package/dist/domain/workflowexecution/subscribe.js.map +1 -1
  41. package/dist/temporal/agentexecution/runner-result.d.ts +12 -0
  42. package/dist/temporal/agentexecution/runner-result.d.ts.map +1 -1
  43. package/dist/temporal/agentexecution/runner-result.js +15 -0
  44. package/dist/temporal/agentexecution/runner-result.js.map +1 -1
  45. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.d.ts.map +1 -1
  46. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js +44 -22
  47. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js.map +1 -1
  48. package/package.json +6 -6
  49. package/src/domain/agentexecution/__tests__/store-faults.test.ts +315 -0
  50. package/src/domain/agentexecution/__tests__/update-status.test.ts +217 -0
  51. package/src/domain/agentexecution/artifacts.ts +21 -12
  52. package/src/domain/agentexecution/lifecycle.ts +5 -3
  53. package/src/domain/agentexecution/platform-rows.ts +133 -0
  54. package/src/domain/agentexecution/subscribe.ts +7 -3
  55. package/src/domain/agentexecution/update-status.ts +70 -19
  56. package/src/domain/workflowexecution/__tests__/store-faults.test.ts +418 -0
  57. package/src/domain/workflowexecution/create-steps.ts +6 -2
  58. package/src/domain/workflowexecution/lifecycle.ts +5 -2
  59. package/src/domain/workflowexecution/send-signal.ts +11 -4
  60. package/src/domain/workflowexecution/submit-approval.ts +6 -2
  61. package/src/domain/workflowexecution/submit-file-decision.ts +6 -2
  62. package/src/domain/workflowexecution/submit-workflow-task-approval.ts +6 -2
  63. package/src/domain/workflowexecution/subscribe-events.ts +6 -2
  64. package/src/domain/workflowexecution/subscribe.ts +11 -4
  65. package/src/pipeline/__tests__/blind-not-found.test.ts +297 -0
  66. package/src/temporal/agentexecution/__tests__/invoke-workflow.test.ts +51 -1
  67. package/src/temporal/agentexecution/__tests__/own-behalf-status-writes.test.ts +149 -9
  68. package/src/temporal/agentexecution/__tests__/replay-histories/runner-failed.json +757 -0
  69. package/src/temporal/agentexecution/__tests__/replay-histories/user-cancel.json +755 -0
  70. package/src/temporal/agentexecution/__tests__/replay.test.ts +4 -2
  71. package/src/temporal/agentexecution/runner-result.ts +16 -0
  72. package/src/temporal/agentexecution/workflows/invoke-agent-execution.ts +49 -22
  73. package/src/domain/agentexecution/__tests__/execution-usage-report.test.ts +0 -78
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Pins the store-fault contract every workflow-execution load shares: a
3
+ * typed ResourceNotFoundError answers NotFound with the surface's own pinned
4
+ * copy, and any other store failure is an infrastructure fault answered as a
5
+ * sanitized Internal, never a NotFound that tells a client the run (or its
6
+ * workflow) does not exist (stigmer/stigmer#1345).
7
+ *
8
+ * The surfaces: the five lifecycle verbs (their shared LoadExecutionById
9
+ * step), sendSignal, the two approval-forwarding verbs, the task-approval
10
+ * signal, subscribe's snapshot read, subscribeEvents' existence check, and
11
+ * create's CreateDefaultInstanceIfNeeded workflow load. The composed suites
12
+ * reach only a real store, which cannot fail selectively, so each surface
13
+ * runs here against a store whose one read throws. Every other dependency is
14
+ * untouchable: a load that fails must stop the call before any of them.
15
+ *
16
+ * Out of scope: the NotFound copy itself (wire contract, pinned where each
17
+ * surface is otherwise tested and by the conformance suites) and loads
18
+ * outside this domain.
19
+ */
20
+ import { create } from "@bufbuild/protobuf";
21
+ import type { HandlerContext } from "@connectrpc/connect";
22
+ import { Code, ConnectError, createContextValues } from "@connectrpc/connect";
23
+ import { describe, expect, it } from "vitest";
24
+
25
+ import {
26
+ ApprovalAction,
27
+ FileDecisionAction,
28
+ FileDecisionScope,
29
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
30
+ import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
31
+ import {
32
+ CancelWorkflowExecutionInputSchema,
33
+ PauseWorkflowExecutionInputSchema,
34
+ RecoverWorkflowExecutionInputSchema,
35
+ ResumeWorkflowExecutionInputSchema,
36
+ SendSignalInputSchema,
37
+ SubmitWorkflowApprovalInputSchema,
38
+ SubmitWorkflowFileDecisionInputSchema,
39
+ SubmitWorkflowTaskApprovalInputSchema,
40
+ SubscribeEventsRequestSchema,
41
+ SubscribeWorkflowExecutionRequestSchema,
42
+ TerminateWorkflowExecutionInputSchema,
43
+ } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
44
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
45
+
46
+ import { createLogger } from "../../../boot/logger.js";
47
+ import { callerIdentityKey } from "../../../pipeline/interceptors/auth.js";
48
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
49
+ import { RequestContext } from "../../../pipeline/request-context.js";
50
+ import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
51
+ import { ResourceNotFoundError } from "../../../store/interface.js";
52
+ import type { Store } from "../../../store/interface.js";
53
+
54
+ import { newCreateDefaultInstanceIfNeededStep } from "../create-steps.js";
55
+ import type { LifecycleDeps } from "../lifecycle.js";
56
+ import {
57
+ cancelExecution,
58
+ pauseExecution,
59
+ recoverExecution,
60
+ resumeExecution,
61
+ terminateExecution,
62
+ } from "../lifecycle.js";
63
+ import { sendSignal } from "../send-signal.js";
64
+ import { StreamBroker } from "../stream-broker.js";
65
+ import { submitApproval } from "../submit-approval.js";
66
+ import { submitFileDecision } from "../submit-file-decision.js";
67
+ import { submitWorkflowTaskApproval } from "../submit-workflow-task-approval.js";
68
+ import { subscribeEvents } from "../subscribe-events.js";
69
+ import { subscribeExecution } from "../subscribe.js";
70
+
71
+ const silentLogger = createLogger({
72
+ level: "error",
73
+ pretty: false,
74
+ write: () => {},
75
+ });
76
+
77
+ const EXECUTION_ID = "wex_storefault";
78
+ const WORKFLOW_ID = "wfl_storefault";
79
+
80
+ const EXECUTION_FAULT_COPY = "failed to load workflow execution";
81
+ const WORKFLOW_FAULT_COPY = "failed to load workflow";
82
+
83
+ /** A dependency the call must never reach once its load has failed. */
84
+ function untouchable<T extends object>(name: string): T {
85
+ return new Proxy({} as T, {
86
+ get(_target, prop) {
87
+ throw new Error(`${name}.${String(prop)} reached after a failed load`);
88
+ },
89
+ });
90
+ }
91
+
92
+ /** A store whose every read fails with the given error. */
93
+ function failingStore(error: Error): Store {
94
+ return {
95
+ getResource: () => Promise.reject(error),
96
+ } as unknown as Store;
97
+ }
98
+
99
+ const MISSING = (): Error =>
100
+ new ResourceNotFoundError(`workflow_execution/${EXECUTION_ID}`);
101
+ const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
102
+
103
+ async function errorOf(call: () => Promise<unknown>): Promise<ConnectError> {
104
+ const error = await call().then(
105
+ () => undefined,
106
+ (e: unknown) => e,
107
+ );
108
+ expect(error, "the call must fail").toBeInstanceOf(ConnectError);
109
+ return error as ConnectError;
110
+ }
111
+
112
+ function handlerContext(): HandlerContext {
113
+ const values = createContextValues();
114
+ values.set(callerIdentityKey, testCallerIdentity());
115
+ return { signal: new AbortController().signal, values } as HandlerContext;
116
+ }
117
+
118
+ function lifecycleDeps(store: Store): LifecycleDeps {
119
+ return {
120
+ store,
121
+ logger: silentLogger,
122
+ authorizer: newPermissiveSingleTeamAuthorizer(),
123
+ broker: untouchable("broker"),
124
+ engineState: untouchable("engineState"),
125
+ executionContextBuilder: untouchable("executionContextBuilder"),
126
+ sandboxLane: untouchable("sandboxLane"),
127
+ temporalConfig: untouchable("temporalConfig"),
128
+ sandboxTerminalObserver: untouchable("sandboxTerminalObserver"),
129
+ gateSteps: new Map(),
130
+ };
131
+ }
132
+
133
+ const LIFECYCLE_VERBS: ReadonlyArray<
134
+ readonly [string, (deps: LifecycleDeps) => Promise<unknown>]
135
+ > = [
136
+ [
137
+ "cancel",
138
+ (deps) =>
139
+ cancelExecution(
140
+ deps,
141
+ create(CancelWorkflowExecutionInputSchema, { id: EXECUTION_ID }),
142
+ testCallerIdentity(),
143
+ ),
144
+ ],
145
+ [
146
+ "terminate",
147
+ (deps) =>
148
+ terminateExecution(
149
+ deps,
150
+ create(TerminateWorkflowExecutionInputSchema, { id: EXECUTION_ID }),
151
+ testCallerIdentity(),
152
+ ),
153
+ ],
154
+ [
155
+ "pause",
156
+ (deps) =>
157
+ pauseExecution(
158
+ deps,
159
+ create(PauseWorkflowExecutionInputSchema, { id: EXECUTION_ID }),
160
+ testCallerIdentity(),
161
+ ),
162
+ ],
163
+ [
164
+ "resume",
165
+ (deps) =>
166
+ resumeExecution(
167
+ deps,
168
+ create(ResumeWorkflowExecutionInputSchema, { id: EXECUTION_ID }),
169
+ testCallerIdentity(),
170
+ ),
171
+ ],
172
+ [
173
+ "recover",
174
+ (deps) =>
175
+ recoverExecution(
176
+ deps,
177
+ create(RecoverWorkflowExecutionInputSchema, { id: EXECUTION_ID }),
178
+ testCallerIdentity(),
179
+ ),
180
+ ],
181
+ ];
182
+
183
+ describe.each(LIFECYCLE_VERBS)("%s — LoadExecutionById", (_verb, run) => {
184
+ it("a missing execution answers NotFound with the lifecycle copy", async () => {
185
+ const error = await errorOf(() =>
186
+ run(lifecycleDeps(failingStore(MISSING()))),
187
+ );
188
+
189
+ expect(error.code).toBe(Code.NotFound);
190
+ expect(error.rawMessage).toBe(
191
+ `workflow_execution not found: ${EXECUTION_ID}`,
192
+ );
193
+ });
194
+
195
+ it("any other store failure answers a sanitized Internal", async () => {
196
+ const error = await errorOf(() =>
197
+ run(lifecycleDeps(failingStore(LOCKED()))),
198
+ );
199
+
200
+ expect(error.code).toBe(Code.Internal);
201
+ expect(error.rawMessage).toBe(EXECUTION_FAULT_COPY);
202
+ });
203
+ });
204
+
205
+ /**
206
+ * The pipeline verbs with their own load step: each runs Authorize and its
207
+ * input validation (a hand-written step or ValidateProto) first, so the
208
+ * inputs carry every field the validation requires, and the load is the
209
+ * first step that reads the store.
210
+ */
211
+ const COMMAND_VERBS: ReadonlyArray<
212
+ readonly [string, string, (store: Store) => Promise<unknown>]
213
+ > = [
214
+ [
215
+ "sendSignal — LoadExecutionByExecutionId",
216
+ `workflow_execution not found: ${EXECUTION_ID}`,
217
+ (store) =>
218
+ sendSignal(
219
+ {
220
+ store,
221
+ logger: silentLogger,
222
+ authorizer: newPermissiveSingleTeamAuthorizer(),
223
+ engineState: untouchable("engineState"),
224
+ },
225
+ create(SendSignalInputSchema, {
226
+ executionId: EXECUTION_ID,
227
+ signalName: "resume",
228
+ }),
229
+ testCallerIdentity(),
230
+ ),
231
+ ],
232
+ [
233
+ "submitApproval — LoadExisting",
234
+ `workflow_execution not found: ${EXECUTION_ID}`,
235
+ (store) =>
236
+ submitApproval(
237
+ {
238
+ store,
239
+ logger: silentLogger,
240
+ authorizer: newPermissiveSingleTeamAuthorizer(),
241
+ approvalForwarder: untouchable("approvalForwarder"),
242
+ },
243
+ create(SubmitWorkflowApprovalInputSchema, {
244
+ executionId: EXECUTION_ID,
245
+ toolCallId: "call_storefault",
246
+ action: ApprovalAction.APPROVE,
247
+ }),
248
+ testCallerIdentity(),
249
+ ),
250
+ ],
251
+ [
252
+ "submitFileDecision — LoadExisting",
253
+ `workflow_execution not found: ${EXECUTION_ID}`,
254
+ (store) =>
255
+ submitFileDecision(
256
+ {
257
+ store,
258
+ logger: silentLogger,
259
+ authorizer: newPermissiveSingleTeamAuthorizer(),
260
+ fileDecisionForwarder: untouchable("fileDecisionForwarder"),
261
+ },
262
+ create(SubmitWorkflowFileDecisionInputSchema, {
263
+ executionId: EXECUTION_ID,
264
+ childAgentExecutionId: "aex_storefault",
265
+ changeSetId: "chs_storefault",
266
+ scope: FileDecisionScope.CHANGE_SET,
267
+ action: FileDecisionAction.APPROVE,
268
+ expectedDigest: "sha256:storefault",
269
+ }),
270
+ testCallerIdentity(),
271
+ ),
272
+ ],
273
+ [
274
+ "submitWorkflowTaskApproval — LoadExecutionForApproval",
275
+ `WorkflowExecution not found: ${EXECUTION_ID}`,
276
+ (store) =>
277
+ submitWorkflowTaskApproval(
278
+ {
279
+ store,
280
+ logger: silentLogger,
281
+ authorizer: newPermissiveSingleTeamAuthorizer(),
282
+ engineState: untouchable("engineState"),
283
+ },
284
+ create(SubmitWorkflowTaskApprovalInputSchema, {
285
+ executionId: EXECUTION_ID,
286
+ taskName: "review",
287
+ outcome: "approved",
288
+ }),
289
+ testCallerIdentity(),
290
+ ),
291
+ ],
292
+ ];
293
+
294
+ describe.each(COMMAND_VERBS)("%s", (_verb, notFoundCopy, run) => {
295
+ it("a missing execution answers NotFound with the verb's copy", async () => {
296
+ const error = await errorOf(() => run(failingStore(MISSING())));
297
+
298
+ expect(error.code).toBe(Code.NotFound);
299
+ expect(error.rawMessage).toBe(notFoundCopy);
300
+ });
301
+
302
+ it("any other store failure answers a sanitized Internal", async () => {
303
+ const error = await errorOf(() => run(failingStore(LOCKED())));
304
+
305
+ expect(error.code).toBe(Code.Internal);
306
+ expect(error.rawMessage).toBe(EXECUTION_FAULT_COPY);
307
+ });
308
+ });
309
+
310
+ describe("subscribe — the snapshot read", () => {
311
+ function subscribeError(
312
+ store: Store,
313
+ broker: StreamBroker,
314
+ ): Promise<ConnectError> {
315
+ return errorOf(() =>
316
+ subscribeExecution(
317
+ {
318
+ store,
319
+ logger: silentLogger,
320
+ broker,
321
+ authorizer: newPermissiveSingleTeamAuthorizer(),
322
+ },
323
+ create(SubscribeWorkflowExecutionRequestSchema, {
324
+ executionId: EXECUTION_ID,
325
+ }),
326
+ handlerContext(),
327
+ ).next(),
328
+ );
329
+ }
330
+
331
+ it("a missing execution answers NotFound with the subscribe copy and releases the subscription", async () => {
332
+ const broker = new StreamBroker(silentLogger);
333
+ const error = await subscribeError(failingStore(MISSING()), broker);
334
+
335
+ expect(error.code).toBe(Code.NotFound);
336
+ expect(error.rawMessage).toBe(
337
+ `WorkflowExecution not found: ${EXECUTION_ID}`,
338
+ );
339
+ expect(broker.getSubscriberCount(EXECUTION_ID)).toBe(0);
340
+ });
341
+
342
+ it("any other store failure answers a sanitized Internal and releases the subscription", async () => {
343
+ const broker = new StreamBroker(silentLogger);
344
+ const error = await subscribeError(failingStore(LOCKED()), broker);
345
+
346
+ expect(error.code).toBe(Code.Internal);
347
+ expect(error.rawMessage).toBe(EXECUTION_FAULT_COPY);
348
+ expect(broker.getSubscriberCount(EXECUTION_ID)).toBe(0);
349
+ });
350
+ });
351
+
352
+ describe("subscribeEvents — the existence check", () => {
353
+ function subscribeEventsError(store: Store): Promise<ConnectError> {
354
+ return errorOf(() =>
355
+ subscribeEvents(
356
+ {
357
+ store,
358
+ logger: silentLogger,
359
+ authorizer: newPermissiveSingleTeamAuthorizer(),
360
+ },
361
+ create(SubscribeEventsRequestSchema, { executionId: EXECUTION_ID }),
362
+ handlerContext(),
363
+ ).next(),
364
+ );
365
+ }
366
+
367
+ it("a missing execution answers NotFound with the stream's copy", async () => {
368
+ const error = await subscribeEventsError(failingStore(MISSING()));
369
+
370
+ expect(error.code).toBe(Code.NotFound);
371
+ expect(error.rawMessage).toBe(
372
+ `WorkflowExecution not found: ${EXECUTION_ID}`,
373
+ );
374
+ });
375
+
376
+ it("any other store failure answers a sanitized Internal", async () => {
377
+ const error = await subscribeEventsError(failingStore(LOCKED()));
378
+
379
+ expect(error.code).toBe(Code.Internal);
380
+ expect(error.rawMessage).toBe(EXECUTION_FAULT_COPY);
381
+ });
382
+ });
383
+
384
+ describe("create — CreateDefaultInstanceIfNeeded's workflow load", () => {
385
+ async function runStep(store: Store): Promise<void> {
386
+ const ctx = new RequestContext(
387
+ WorkflowExecutionSchema,
388
+ create(WorkflowExecutionSchema, { spec: { workflowId: WORKFLOW_ID } }),
389
+ testCallerIdentity(),
390
+ ApiResourceKind.workflow_execution,
391
+ );
392
+ await newCreateDefaultInstanceIfNeededStep({
393
+ store,
394
+ logger: silentLogger,
395
+ workflowInstanceCreator: () => {
396
+ throw new Error("workflowInstanceCreator reached after a failed load");
397
+ },
398
+ }).execute(ctx);
399
+ }
400
+
401
+ it("a missing workflow answers NotFound with the create copy", async () => {
402
+ const error = await errorOf(() =>
403
+ runStep(
404
+ failingStore(new ResourceNotFoundError(`workflow/${WORKFLOW_ID}`)),
405
+ ),
406
+ );
407
+
408
+ expect(error.code).toBe(Code.NotFound);
409
+ expect(error.rawMessage).toBe(`Workflow not found: ${WORKFLOW_ID}`);
410
+ });
411
+
412
+ it("any other store failure answers a sanitized Internal", async () => {
413
+ const error = await errorOf(() => runStep(failingStore(LOCKED())));
414
+
415
+ expect(error.code).toBe(Code.Internal);
416
+ expect(error.rawMessage).toBe(WORKFLOW_FAULT_COPY);
417
+ });
418
+ });
@@ -35,6 +35,7 @@ import {
35
35
  notFoundError,
36
36
  } from "../../pipeline/errors.js";
37
37
  import type { PipelineStep } from "../../pipeline/pipeline.js";
38
+ import { ResourceNotFoundError } from "../../store/interface.js";
38
39
  import type { Store } from "../../store/interface.js";
39
40
  import { fromBinary } from "@bufbuild/protobuf";
40
41
 
@@ -127,8 +128,11 @@ export function newCreateDefaultInstanceIfNeededStep(
127
128
  workflowId,
128
129
  WorkflowSchema,
129
130
  );
130
- } catch {
131
- throw notFoundError("Workflow", workflowId);
131
+ } catch (error) {
132
+ if (error instanceof ResourceNotFoundError) {
133
+ throw notFoundError("Workflow", workflowId);
134
+ }
135
+ throw internalError(error, "failed to load workflow");
132
136
  }
133
137
 
134
138
  const defaultInstanceId = workflow.status?.defaultInstanceId ?? "";
@@ -149,8 +149,11 @@ function newLoadExecutionByIdStep<Desc extends DescMessage>(
149
149
  executionId,
150
150
  WorkflowExecutionSchema,
151
151
  );
152
- } catch {
153
- throw notFoundError("workflow_execution", executionId);
152
+ } catch (error) {
153
+ if (error instanceof ResourceNotFoundError) {
154
+ throw notFoundError("workflow_execution", executionId);
155
+ }
156
+ throw internalError(error, "failed to load workflow execution");
154
157
  }
155
158
  ctx.set(LOADED_EXECUTION_KEY, execution);
156
159
  },
@@ -13,7 +13,8 @@
13
13
  * Codes (pinned): InvalidArgument (missing fields), NotFound (unknown
14
14
  * execution), FailedPrecondition (terminal phase, or no engine),
15
15
  * AlreadyExists (DELIVERED duplicate — stop retrying), Aborted (live
16
- * in-flight claim — retry shortly). Dedupe-store errors degrade to
16
+ * in-flight claim — retry shortly), Internal (a store fault loading the
17
+ * execution, never folded into NotFound). Dedupe-store errors degrade to
17
18
  * no-dedupe rather than failing the send; a failed send RELEASES the
18
19
  * claim (status-guarded) so the retry claims freshly instead of waiting
19
20
  * out the in-flight hold.
@@ -41,7 +42,10 @@ import { newPipeline } from "../../pipeline/pipeline.js";
41
42
  import type { CallerIdentity } from "../../extensions/identity.js";
42
43
  import { RequestContext } from "../../pipeline/request-context.js";
43
44
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
44
- import { IN_FLIGHT_CLAIM_TTL_MS } from "../../store/interface.js";
45
+ import {
46
+ IN_FLIGHT_CLAIM_TTL_MS,
47
+ ResourceNotFoundError,
48
+ } from "../../store/interface.js";
45
49
  import type { Store } from "../../store/interface.js";
46
50
 
47
51
  import {
@@ -109,8 +113,11 @@ export async function sendSignal(
109
113
  ctx.input.executionId,
110
114
  WorkflowExecutionSchema,
111
115
  );
112
- } catch {
113
- throw notFoundError("workflow_execution", ctx.input.executionId);
116
+ } catch (error) {
117
+ if (error instanceof ResourceNotFoundError) {
118
+ throw notFoundError("workflow_execution", ctx.input.executionId);
119
+ }
120
+ throw internalError(error, "failed to load workflow execution");
114
121
  }
115
122
  ctx.set(LOADED_EXECUTION_KEY, execution);
116
123
  },
@@ -38,6 +38,7 @@ import type { CallerIdentity } from "../../extensions/identity.js";
38
38
  import { RequestContext } from "../../pipeline/request-context.js";
39
39
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
40
40
  import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
41
+ import { ResourceNotFoundError } from "../../store/interface.js";
41
42
  import type { Store } from "../../store/interface.js";
42
43
 
43
44
  /**
@@ -101,8 +102,11 @@ export async function submitApproval(
101
102
  ctx.input.executionId,
102
103
  WorkflowExecutionSchema,
103
104
  );
104
- } catch {
105
- throw notFoundError("workflow_execution", ctx.input.executionId);
105
+ } catch (error) {
106
+ if (error instanceof ResourceNotFoundError) {
107
+ throw notFoundError("workflow_execution", ctx.input.executionId);
108
+ }
109
+ throw internalError(error, "failed to load workflow execution");
106
110
  }
107
111
  ctx.set(TARGET_EXECUTION_KEY, execution);
108
112
  },
@@ -36,6 +36,7 @@ import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
36
36
  import type { CallerIdentity } from "../../extensions/identity.js";
37
37
  import { RequestContext } from "../../pipeline/request-context.js";
38
38
  import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
39
+ import { ResourceNotFoundError } from "../../store/interface.js";
39
40
  import type { Store } from "../../store/interface.js";
40
41
 
41
42
  /**
@@ -99,8 +100,11 @@ export async function submitFileDecision(
99
100
  ctx.input.executionId,
100
101
  WorkflowExecutionSchema,
101
102
  );
102
- } catch {
103
- throw notFoundError("workflow_execution", ctx.input.executionId);
103
+ } catch (error) {
104
+ if (error instanceof ResourceNotFoundError) {
105
+ throw notFoundError("workflow_execution", ctx.input.executionId);
106
+ }
107
+ throw internalError(error, "failed to load workflow execution");
104
108
  }
105
109
  ctx.set(TARGET_EXECUTION_KEY, execution);
106
110
  },
@@ -40,6 +40,7 @@ import type { Authorizer } from "../../extensions/authorizer.js";
40
40
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
41
41
  import type { CallerIdentity } from "../../extensions/identity.js";
42
42
  import { RequestContext } from "../../pipeline/request-context.js";
43
+ import { ResourceNotFoundError } from "../../store/interface.js";
43
44
  import type { Store } from "../../store/interface.js";
44
45
 
45
46
  import {
@@ -106,8 +107,11 @@ export async function submitWorkflowTaskApproval(
106
107
  ctx.input.executionId,
107
108
  WorkflowExecutionSchema,
108
109
  );
109
- } catch {
110
- throw notFoundError("WorkflowExecution", ctx.input.executionId);
110
+ } catch (error) {
111
+ if (error instanceof ResourceNotFoundError) {
112
+ throw notFoundError("WorkflowExecution", ctx.input.executionId);
113
+ }
114
+ throw internalError(error, "failed to load workflow execution");
111
115
  }
112
116
  ctx.set(LOADED_EXECUTION_KEY, execution);
113
117
  },
@@ -44,6 +44,7 @@ import {
44
44
  notFoundError,
45
45
  } from "../../pipeline/errors.js";
46
46
  import { authorizeDirect } from "../../pipeline/steps/authorize.js";
47
+ import { ResourceNotFoundError } from "../../store/interface.js";
47
48
  import type {
48
49
  Store,
49
50
  WorkflowExecutionEventRecord,
@@ -88,8 +89,11 @@ export async function* subscribeEvents(
88
89
  executionId,
89
90
  WorkflowExecutionSchema,
90
91
  );
91
- } catch {
92
- throw notFoundError("WorkflowExecution", executionId);
92
+ } catch (error) {
93
+ if (error instanceof ResourceNotFoundError) {
94
+ throw notFoundError("WorkflowExecution", executionId);
95
+ }
96
+ throw internalError(error, "failed to load workflow execution");
93
97
  }
94
98
 
95
99
  let typeFilter: Set<string> | undefined;
@@ -40,8 +40,13 @@ import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/age
40
40
  import type { Logger } from "../../boot/logger.js";
41
41
  import type { Authorizer } from "../../extensions/authorizer.js";
42
42
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
43
- import { invalidArgumentError, notFoundError } from "../../pipeline/errors.js";
43
+ import {
44
+ internalError,
45
+ invalidArgumentError,
46
+ notFoundError,
47
+ } from "../../pipeline/errors.js";
44
48
  import { authorizeDirect } from "../../pipeline/steps/authorize.js";
49
+ import { ResourceNotFoundError } from "../../store/interface.js";
45
50
  import type { Store } from "../../store/interface.js";
46
51
 
47
52
  import type { StreamBroker } from "./stream-broker.js";
@@ -103,9 +108,11 @@ export async function* subscribeExecution(
103
108
  id,
104
109
  WorkflowExecutionSchema,
105
110
  );
106
- } catch {
107
- // Go converts every load failure here to the same NotFound.
108
- throw notFoundError("WorkflowExecution", id);
111
+ } catch (error) {
112
+ if (error instanceof ResourceNotFoundError) {
113
+ throw notFoundError("WorkflowExecution", id);
114
+ }
115
+ throw internalError(error, "failed to load workflow execution");
109
116
  }
110
117
 
111
118
  yield snapshot;