@stigmer/server 3.39.0 → 3.39.1-dev.20260928194002
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/domain/agentinstance/steps.d.ts +3 -1
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +12 -6
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +10 -5
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +7 -3
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.js +6 -3
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts +2 -2
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +10 -5
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +5 -3
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +14 -9
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/package.json +6 -6
- package/src/domain/agentinstance/__tests__/store-faults.test.ts +69 -18
- package/src/domain/agentinstance/steps.ts +12 -6
- package/src/domain/plugin/__tests__/store-faults.test.ts +104 -0
- package/src/domain/plugin/controller.ts +10 -5
- package/src/domain/skill/__tests__/store-faults.test.ts +103 -0
- package/src/domain/skill/controller.ts +7 -3
- package/src/domain/workflow/__tests__/store-faults.test.ts +107 -0
- package/src/domain/workflow/controller.ts +6 -3
- package/src/domain/workflowinstance/__tests__/store-faults.test.ts +172 -0
- package/src/domain/workflowinstance/controller.ts +12 -7
- package/src/domain/workflowinstance/steps.ts +14 -10
- package/src/pipeline/__tests__/blind-not-found.test.ts +4 -20
|
@@ -99,6 +99,7 @@ import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-
|
|
|
99
99
|
import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
|
|
100
100
|
import { newValidateVisibilityUpdateStep } from "../../pipeline/steps/validate-visibility.js";
|
|
101
101
|
import { newDeleteVersionArchivesStep } from "../../pipeline/steps/version-archive.js";
|
|
102
|
+
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
102
103
|
import type { Store } from "../../store/interface.js";
|
|
103
104
|
import { MAX_ZIP_SIZE, TRANSFER_LANE_NOT_CONFIGURED } from "./constants.js";
|
|
104
105
|
import {
|
|
@@ -463,7 +464,7 @@ async function updateVisibility(
|
|
|
463
464
|
return reqCtx.get(UPDATE_VISIBILITY_SKILL_KEY) as Skill;
|
|
464
465
|
}
|
|
465
466
|
|
|
466
|
-
/** Loads the skill by resource_id;
|
|
467
|
+
/** Loads the skill by resource_id; a missing one answers NotFound, a store fault Internal. */
|
|
467
468
|
function newLoadSkillForVisibilityUpdateStep(
|
|
468
469
|
store: Store,
|
|
469
470
|
): PipelineStep<UpdateVisibilityDesc> {
|
|
@@ -478,8 +479,11 @@ function newLoadSkillForVisibilityUpdateStep(
|
|
|
478
479
|
input.resourceId,
|
|
479
480
|
SkillSchema,
|
|
480
481
|
);
|
|
481
|
-
} catch {
|
|
482
|
-
|
|
482
|
+
} catch (error) {
|
|
483
|
+
if (error instanceof ResourceNotFoundError) {
|
|
484
|
+
throw notFoundError("skill", input.resourceId);
|
|
485
|
+
}
|
|
486
|
+
throw internalError(error, "failed to load skill");
|
|
483
487
|
}
|
|
484
488
|
ctx.set(UPDATE_VISIBILITY_SKILL_KEY, skill);
|
|
485
489
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the store-fault contract of the workflow's visibility-update load: a
|
|
3
|
+
* typed ResourceNotFoundError answers NotFound with the domain's pinned copy
|
|
4
|
+
* (`workflow not found: <id>`), and any other store failure is an
|
|
5
|
+
* infrastructure fault answered as a sanitized Internal, never a NotFound that
|
|
6
|
+
* tells a client the workflow does not exist (stigmer/stigmer#1345).
|
|
7
|
+
*
|
|
8
|
+
* The surface is updateVisibility, reached through the registered handler on
|
|
9
|
+
* an in-process router. The composed suites reach only a real store, which
|
|
10
|
+
* cannot fail selectively, so it runs here against a store whose read throws.
|
|
11
|
+
* The validator and the default-instance creator are untouchable: a load that
|
|
12
|
+
* fails must stop the call before anything past it runs.
|
|
13
|
+
*
|
|
14
|
+
* Out of scope: the NotFound copy itself (wire contract).
|
|
15
|
+
*/
|
|
16
|
+
import type { Client, ConnectError } from "@connectrpc/connect";
|
|
17
|
+
import { Code, createClient, createRouterTransport } from "@connectrpc/connect";
|
|
18
|
+
import { describe, expect, it } from "vitest";
|
|
19
|
+
|
|
20
|
+
import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
|
|
21
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
22
|
+
|
|
23
|
+
import { createLogger } from "../../../boot/logger.js";
|
|
24
|
+
import { createApiResourceInterceptor } from "../../../pipeline/interceptors/apiresource.js";
|
|
25
|
+
import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
|
|
26
|
+
import {
|
|
27
|
+
errorOf,
|
|
28
|
+
failingStore,
|
|
29
|
+
untouchable,
|
|
30
|
+
} from "../../../pipeline/__tests__/support.js";
|
|
31
|
+
import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
|
|
32
|
+
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
33
|
+
import type { Store } from "../../../store/interface.js";
|
|
34
|
+
|
|
35
|
+
import { registerWorkflowServices } from "../controller.js";
|
|
36
|
+
|
|
37
|
+
const silentLogger = createLogger({
|
|
38
|
+
level: "error",
|
|
39
|
+
pretty: false,
|
|
40
|
+
write: () => {},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const WORKFLOW_ID = "wfl_storefault";
|
|
44
|
+
|
|
45
|
+
const NOT_FOUND_COPY = `workflow not found: ${WORKFLOW_ID}`;
|
|
46
|
+
const LOAD_FAULT_COPY = "failed to load workflow";
|
|
47
|
+
|
|
48
|
+
const MISSING = (): Error =>
|
|
49
|
+
new ResourceNotFoundError(`workflow/${WORKFLOW_ID}`);
|
|
50
|
+
const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The registered handlers on an in-process router: the verifier chain stamps
|
|
54
|
+
* the trusted-local caller and the apiresource interceptor the kind, then
|
|
55
|
+
* Authorize and ValidateProto run and the load is the first step that reads
|
|
56
|
+
* the store.
|
|
57
|
+
*/
|
|
58
|
+
function workflowCommand(
|
|
59
|
+
store: Store,
|
|
60
|
+
): Client<typeof WorkflowCommandController> {
|
|
61
|
+
const transport = createRouterTransport(
|
|
62
|
+
(router) => {
|
|
63
|
+
registerWorkflowServices(router, {
|
|
64
|
+
store,
|
|
65
|
+
logger: silentLogger,
|
|
66
|
+
authorizer: newPermissiveSingleTeamAuthorizer(),
|
|
67
|
+
authorizationLifecycle: undefined,
|
|
68
|
+
validator: untouchable("validator"),
|
|
69
|
+
workflowInstanceCreator: untouchable("workflowInstanceCreator"),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
router: {
|
|
74
|
+
interceptors: [
|
|
75
|
+
createVerifierChainInterceptor([], [], silentLogger),
|
|
76
|
+
createApiResourceInterceptor(),
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
return createClient(WorkflowCommandController, transport);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("updateVisibility — LoadWorkflowForVisibilityUpdate", () => {
|
|
85
|
+
function surfaceError(store: Store): Promise<ConnectError> {
|
|
86
|
+
return errorOf(() =>
|
|
87
|
+
workflowCommand(store).updateVisibility({
|
|
88
|
+
resourceId: WORKFLOW_ID,
|
|
89
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
it("a missing workflow answers NotFound with the domain's copy", async () => {
|
|
95
|
+
const error = await surfaceError(failingStore(MISSING()));
|
|
96
|
+
|
|
97
|
+
expect(error.code).toBe(Code.NotFound);
|
|
98
|
+
expect(error.rawMessage).toBe(NOT_FOUND_COPY);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("any other store failure answers a sanitized Internal", async () => {
|
|
102
|
+
const error = await surfaceError(failingStore(LOCKED()));
|
|
103
|
+
|
|
104
|
+
expect(error.code).toBe(Code.Internal);
|
|
105
|
+
expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -492,7 +492,7 @@ async function updateVisibility(
|
|
|
492
492
|
return reqCtx.get(UPDATE_VISIBILITY_WORKFLOW_KEY) as Workflow;
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
/** Loads the workflow by resource_id;
|
|
495
|
+
/** Loads the workflow by resource_id; a missing one answers NotFound, a store fault Internal. */
|
|
496
496
|
function newLoadWorkflowForVisibilityUpdateStep(
|
|
497
497
|
store: Store,
|
|
498
498
|
): PipelineStep<UpdateVisibilityDesc> {
|
|
@@ -507,8 +507,11 @@ function newLoadWorkflowForVisibilityUpdateStep(
|
|
|
507
507
|
input.resourceId,
|
|
508
508
|
WorkflowSchema,
|
|
509
509
|
);
|
|
510
|
-
} catch {
|
|
511
|
-
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (error instanceof ResourceNotFoundError) {
|
|
512
|
+
throw notFoundError("workflow", input.resourceId);
|
|
513
|
+
}
|
|
514
|
+
throw internalError(error, "failed to load workflow");
|
|
512
515
|
}
|
|
513
516
|
ctx.set(UPDATE_VISIBILITY_WORKFLOW_KEY, workflow);
|
|
514
517
|
},
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the store-fault contract of the workflow instance's loads. The shared
|
|
3
|
+
* targeted-update load (updateVisibility and updateExecutionVisibility): a
|
|
4
|
+
* typed ResourceNotFoundError answers NotFound with the domain's pinned copy
|
|
5
|
+
* (`workflow instance not found: <id>`), and any other store failure is an
|
|
6
|
+
* infrastructure fault answered as a sanitized Internal, never a NotFound that
|
|
7
|
+
* tells a client the instance does not exist (stigmer/stigmer#1345). Create's
|
|
8
|
+
* parent-workflow load reads the workflow through the in-process query
|
|
9
|
+
* client, so the store's typed miss arrives as a Connect NotFound: that
|
|
10
|
+
* answers the pinned `Workflow not found: <id>`, and any other failure an
|
|
11
|
+
* Internal, never a missing parent (stigmer/stigmer#1351).
|
|
12
|
+
*
|
|
13
|
+
* Every surface is reached through the registered handler on an in-process
|
|
14
|
+
* router. The composed suites reach only a real store, which cannot fail
|
|
15
|
+
* selectively, so the updates run against a store whose read throws and
|
|
16
|
+
* create against a parent loader whose read throws. Whatever the surface does
|
|
17
|
+
* not need is untouchable: a load that fails must stop the call before
|
|
18
|
+
* anything past it runs.
|
|
19
|
+
*
|
|
20
|
+
* Out of scope: the NotFound copies themselves (wire contract).
|
|
21
|
+
*/
|
|
22
|
+
import type { Client } from "@connectrpc/connect";
|
|
23
|
+
import {
|
|
24
|
+
Code,
|
|
25
|
+
ConnectError,
|
|
26
|
+
createClient,
|
|
27
|
+
createRouterTransport,
|
|
28
|
+
} from "@connectrpc/connect";
|
|
29
|
+
import { describe, expect, it } from "vitest";
|
|
30
|
+
|
|
31
|
+
import { WorkflowInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/command_pb";
|
|
32
|
+
import { WorkflowExecutionVisibility } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/spec_pb";
|
|
33
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
34
|
+
|
|
35
|
+
import { createLogger } from "../../../boot/logger.js";
|
|
36
|
+
import { createApiResourceInterceptor } from "../../../pipeline/interceptors/apiresource.js";
|
|
37
|
+
import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
|
|
38
|
+
import {
|
|
39
|
+
errorOf,
|
|
40
|
+
failingStore,
|
|
41
|
+
untouchable,
|
|
42
|
+
} from "../../../pipeline/__tests__/support.js";
|
|
43
|
+
import { INTERNAL_FALLBACK_MESSAGE } from "../../../pipeline/pipeline.js";
|
|
44
|
+
import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
|
|
45
|
+
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
46
|
+
import type { Store } from "../../../store/interface.js";
|
|
47
|
+
|
|
48
|
+
import { registerWorkflowInstanceServices } from "../controller.js";
|
|
49
|
+
import type { ParentWorkflowLoaderProvider } from "../steps.js";
|
|
50
|
+
|
|
51
|
+
const silentLogger = createLogger({
|
|
52
|
+
level: "error",
|
|
53
|
+
pretty: false,
|
|
54
|
+
write: () => {},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const INSTANCE_ID = "win_storefault";
|
|
58
|
+
const WORKFLOW_ID = "wfl_storefault";
|
|
59
|
+
|
|
60
|
+
const NOT_FOUND_COPY = `workflow instance not found: ${INSTANCE_ID}`;
|
|
61
|
+
const LOAD_FAULT_COPY = "failed to load workflow instance";
|
|
62
|
+
const PARENT_NOT_FOUND_COPY = `Workflow not found: ${WORKFLOW_ID}`;
|
|
63
|
+
const PARENT_LOAD_FAULT_COPY = "failed to load parent workflow";
|
|
64
|
+
|
|
65
|
+
const MISSING = (): Error =>
|
|
66
|
+
new ResourceNotFoundError(`workflow_instance/${INSTANCE_ID}`);
|
|
67
|
+
const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
|
|
68
|
+
|
|
69
|
+
/** What the workflow query's `get` answers for a missing and a faulted workflow. */
|
|
70
|
+
const PARENT_MISSING = (): Error =>
|
|
71
|
+
new ConnectError(`Workflow not found: ${WORKFLOW_ID}`, Code.NotFound);
|
|
72
|
+
const PARENT_FAULTED = (): Error =>
|
|
73
|
+
new ConnectError(INTERNAL_FALLBACK_MESSAGE, Code.Internal);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The registered handlers on an in-process router: the verifier chain stamps
|
|
77
|
+
* the trusted-local caller and the apiresource interceptor the kind, then
|
|
78
|
+
* Authorize and ValidateProto run and the load is the first step that reads
|
|
79
|
+
* the store or the parent.
|
|
80
|
+
*/
|
|
81
|
+
function instanceCommand(
|
|
82
|
+
store: Store,
|
|
83
|
+
parentWorkflowLoader: ParentWorkflowLoaderProvider = untouchable(
|
|
84
|
+
"parentWorkflowLoader",
|
|
85
|
+
),
|
|
86
|
+
): Client<typeof WorkflowInstanceCommandController> {
|
|
87
|
+
const transport = createRouterTransport(
|
|
88
|
+
(router) => {
|
|
89
|
+
registerWorkflowInstanceServices(router, {
|
|
90
|
+
store,
|
|
91
|
+
logger: silentLogger,
|
|
92
|
+
authorizer: newPermissiveSingleTeamAuthorizer(),
|
|
93
|
+
authorizationLifecycle: undefined,
|
|
94
|
+
parentWorkflowLoader,
|
|
95
|
+
listReadScope: undefined,
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
router: {
|
|
100
|
+
interceptors: [
|
|
101
|
+
createVerifierChainInterceptor([], [], silentLogger),
|
|
102
|
+
createApiResourceInterceptor(),
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
return createClient(WorkflowInstanceCommandController, transport);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe.each([
|
|
111
|
+
{
|
|
112
|
+
surface: "updateVisibility — LoadInstanceForVisibilityUpdate",
|
|
113
|
+
call: (store: Store) =>
|
|
114
|
+
instanceCommand(store).updateVisibility({
|
|
115
|
+
resourceId: INSTANCE_ID,
|
|
116
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
surface:
|
|
121
|
+
"updateExecutionVisibility — LoadInstanceForExecutionVisibilityUpdate",
|
|
122
|
+
call: (store: Store) =>
|
|
123
|
+
instanceCommand(store).updateExecutionVisibility({
|
|
124
|
+
resourceId: INSTANCE_ID,
|
|
125
|
+
executionVisibility: WorkflowExecutionVisibility.private,
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
])("$surface", ({ call }) => {
|
|
129
|
+
it("a missing workflow instance answers NotFound with the domain's copy", async () => {
|
|
130
|
+
const error = await errorOf(() => call(failingStore(MISSING())));
|
|
131
|
+
|
|
132
|
+
expect(error.code).toBe(Code.NotFound);
|
|
133
|
+
expect(error.rawMessage).toBe(NOT_FOUND_COPY);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("any other store failure answers a sanitized Internal", async () => {
|
|
137
|
+
const error = await errorOf(() => call(failingStore(LOCKED())));
|
|
138
|
+
|
|
139
|
+
expect(error.code).toBe(Code.Internal);
|
|
140
|
+
expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("create — LoadParentWorkflow", () => {
|
|
145
|
+
function surfaceError(parentError: Error): Promise<ConnectError> {
|
|
146
|
+
const failingParent: ParentWorkflowLoaderProvider = () => ({
|
|
147
|
+
get: () => Promise.reject(parentError),
|
|
148
|
+
});
|
|
149
|
+
return errorOf(() =>
|
|
150
|
+
instanceCommand(untouchable("store"), failingParent).create({
|
|
151
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
152
|
+
kind: "WorkflowInstance",
|
|
153
|
+
metadata: { name: "Store Fault Instance", org: "org_storefault" },
|
|
154
|
+
spec: { workflowId: WORKFLOW_ID },
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
it("a missing parent workflow answers NotFound with the pinned copy", async () => {
|
|
160
|
+
const error = await surfaceError(PARENT_MISSING());
|
|
161
|
+
|
|
162
|
+
expect(error.code).toBe(Code.NotFound);
|
|
163
|
+
expect(error.rawMessage).toBe(PARENT_NOT_FOUND_COPY);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("any other parent-load failure answers a sanitized Internal", async () => {
|
|
167
|
+
const error = await surfaceError(PARENT_FAULTED());
|
|
168
|
+
|
|
169
|
+
expect(error.code).toBe(Code.Internal);
|
|
170
|
+
expect(error.rawMessage).toBe(PARENT_LOAD_FAULT_COPY);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Pipeline per RPC mirrors the Go step chains character-for-character.
|
|
15
15
|
* Proven by workflowinstance.conformance.test.ts
|
|
16
|
-
* (CONFORMANCE_TARGET=local) and the family
|
|
17
|
-
* ../workflow/__tests__/workflow.test.ts (the mutual edge makes the two
|
|
16
|
+
* (CONFORMANCE_TARGET=local), __tests__/store-faults.test.ts and the family
|
|
17
|
+
* test ../workflow/__tests__/workflow.test.ts (the mutual edge makes the two
|
|
18
18
|
* domains one testable unit).
|
|
19
19
|
*/
|
|
20
20
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
@@ -104,6 +104,7 @@ import {
|
|
|
104
104
|
newValidateVisibilityStep,
|
|
105
105
|
newValidateVisibilityUpdateStep,
|
|
106
106
|
} from "../../pipeline/steps/validate-visibility.js";
|
|
107
|
+
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
107
108
|
import type { Store } from "../../store/interface.js";
|
|
108
109
|
import { workflowInstanceSearchExtractor } from "./search-extractor.js";
|
|
109
110
|
import {
|
|
@@ -573,9 +574,10 @@ function newSetInstanceExecutionVisibilityStep(): PipelineStep<UpdateExecutionVi
|
|
|
573
574
|
// ---------------------------------------------------------------------------
|
|
574
575
|
|
|
575
576
|
/**
|
|
576
|
-
* Loads the instance by resource_id;
|
|
577
|
-
* accessor keeps the input shape compiler-checked (both
|
|
578
|
-
* inputs carry resource_id, but the compiler should prove it,
|
|
577
|
+
* Loads the instance by resource_id; a missing one answers NotFound, a store
|
|
578
|
+
* fault Internal. The accessor keeps the input shape compiler-checked (both
|
|
579
|
+
* targeted-update inputs carry resource_id, but the compiler should prove it,
|
|
580
|
+
* not a cast).
|
|
579
581
|
*/
|
|
580
582
|
function newLoadInstanceStep<Desc extends DescMessage>(
|
|
581
583
|
store: Store,
|
|
@@ -594,8 +596,11 @@ function newLoadInstanceStep<Desc extends DescMessage>(
|
|
|
594
596
|
resourceId,
|
|
595
597
|
WorkflowInstanceSchema,
|
|
596
598
|
);
|
|
597
|
-
} catch {
|
|
598
|
-
|
|
599
|
+
} catch (error) {
|
|
600
|
+
if (error instanceof ResourceNotFoundError) {
|
|
601
|
+
throw notFoundError("workflow instance", resourceId);
|
|
602
|
+
}
|
|
603
|
+
throw internalError(error, "failed to load workflow instance");
|
|
599
604
|
}
|
|
600
605
|
ctx.set(instanceKey, instance);
|
|
601
606
|
},
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the family test ../workflow/__tests__/workflow.test.ts (the mutual edge
|
|
9
9
|
* makes the two domains one testable unit).
|
|
10
10
|
*/
|
|
11
|
-
import { ConnectError } from "@connectrpc/connect";
|
|
11
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
12
12
|
import type { DescMessage } from "@bufbuild/protobuf";
|
|
13
13
|
|
|
14
14
|
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
@@ -55,9 +55,11 @@ export type ParentWorkflowLoaderProvider = () => ParentWorkflowLoader;
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* LoadParentWorkflow — create.go: loads the workflow the instance
|
|
58
|
-
* materializes and stashes it for the same-org rule.
|
|
59
|
-
*
|
|
60
|
-
* (capital-W copy is the
|
|
58
|
+
* materializes and stashes it for the same-org rule. The workflow is read
|
|
59
|
+
* through the in-process query client, so a missing one arrives as a Connect
|
|
60
|
+
* NotFound and answers NotFound("Workflow", id) (the capital-W copy is the
|
|
61
|
+
* wire contract); any other failure is a fault answered as Internal, never a
|
|
62
|
+
* missing parent.
|
|
61
63
|
*/
|
|
62
64
|
export function newLoadParentWorkflowStep(
|
|
63
65
|
loaderProvider: ParentWorkflowLoaderProvider,
|
|
@@ -74,12 +76,14 @@ export function newLoadParentWorkflowStep(
|
|
|
74
76
|
try {
|
|
75
77
|
parent = await loaderProvider().get(workflowId);
|
|
76
78
|
} catch (error) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
error
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) {
|
|
80
|
+
logger.warn("Parent workflow not found", {
|
|
81
|
+
error: error.rawMessage,
|
|
82
|
+
workflowId,
|
|
83
|
+
});
|
|
84
|
+
throw notFoundError("Workflow", workflowId);
|
|
85
|
+
}
|
|
86
|
+
throw internalError(error, "failed to load parent workflow");
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
ctx.set(PARENT_WORKFLOW_KEY, parent);
|
|
@@ -20,9 +20,8 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The scan parses each module with the TypeScript compiler API rather than a
|
|
22
22
|
* regular expression, because a catch block's extent (nested braces, a log
|
|
23
|
-
* line before the throw) is a syntax question.
|
|
24
|
-
*
|
|
25
|
-
* blind site fails here, and so does a fixed site still listed.
|
|
23
|
+
* line before the throw) is a syntax question. The scan must find no blind
|
|
24
|
+
* site at all, so a new one fails here with its module and line.
|
|
26
25
|
*/
|
|
27
26
|
import { readFileSync, readdirSync } from "node:fs";
|
|
28
27
|
import path from "node:path";
|
|
@@ -34,19 +33,6 @@ import { describe, expect, it } from "vitest";
|
|
|
34
33
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
35
34
|
const SRC = path.resolve(HERE, "../..");
|
|
36
35
|
|
|
37
|
-
/** Known blind sites by module (relative to `src`), with their count. */
|
|
38
|
-
const PENDING: ReadonlyMap<string, number> = new Map([
|
|
39
|
-
// Store loads, stigmer/stigmer#1345 (fixed domain by domain).
|
|
40
|
-
["domain/plugin/controller.ts", 1],
|
|
41
|
-
["domain/skill/controller.ts", 1],
|
|
42
|
-
["domain/workflow/controller.ts", 1],
|
|
43
|
-
["domain/workflowinstance/controller.ts", 1],
|
|
44
|
-
// Parent loads through the in-process chain that fold every RPC error,
|
|
45
|
-
// validation included, stigmer/stigmer#1351.
|
|
46
|
-
["domain/agentinstance/steps.ts", 1],
|
|
47
|
-
["domain/workflowinstance/steps.ts", 1],
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
36
|
interface BlindSite {
|
|
51
37
|
readonly module: string;
|
|
52
38
|
readonly line: number;
|
|
@@ -258,7 +244,7 @@ describe("the blind-NotFound rule", () => {
|
|
|
258
244
|
});
|
|
259
245
|
|
|
260
246
|
describe("the server source", () => {
|
|
261
|
-
it("has no blind NotFound
|
|
247
|
+
it("has no blind NotFound", () => {
|
|
262
248
|
const sources = serverSources();
|
|
263
249
|
const constructors = notFoundConstructors(sources);
|
|
264
250
|
const found = new Map<string, number[]>();
|
|
@@ -278,8 +264,6 @@ describe("the server source", () => {
|
|
|
278
264
|
const where = [...found]
|
|
279
265
|
.flatMap(([module, lines]) => lines.map((line) => `${module}:${line}`))
|
|
280
266
|
.join("\n");
|
|
281
|
-
expect(counts, `blind NotFound sites found:\n${where}`).toEqual(
|
|
282
|
-
Object.fromEntries(PENDING),
|
|
283
|
-
);
|
|
267
|
+
expect(counts, `blind NotFound sites found:\n${where}`).toEqual({});
|
|
284
268
|
});
|
|
285
269
|
});
|