@stigmer/server 3.38.2 → 3.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domain/agent/controller.d.ts +2 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +7 -3
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts +2 -2
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +10 -5
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -3
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/update-status.d.ts +9 -2
- package/dist/domain/agentexecution/update-status.d.ts.map +1 -1
- package/dist/domain/agentexecution/update-status.js +28 -5
- package/dist/domain/agentexecution/update-status.js.map +1 -1
- package/dist/domain/agentexecution/validate-thinking-mode.d.ts +31 -21
- package/dist/domain/agentexecution/validate-thinking-mode.d.ts.map +1 -1
- package/dist/domain/agentexecution/validate-thinking-mode.js +61 -18
- package/dist/domain/agentexecution/validate-thinking-mode.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +2 -2
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +7 -3
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts +2 -2
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +7 -3
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/artifact/controller.d.ts.map +1 -1
- package/dist/domain/artifact/controller.js +9 -6
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts +2 -2
- package/dist/domain/environment/controller.js +7 -3
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/environment/steps.d.ts +2 -1
- package/dist/domain/environment/steps.d.ts.map +1 -1
- package/dist/domain/environment/steps.js +10 -7
- package/dist/domain/environment/steps.js.map +1 -1
- package/dist/domain/organization/controller.d.ts.map +1 -1
- package/dist/domain/organization/controller.js +33 -14
- package/dist/domain/organization/controller.js.map +1 -1
- package/dist/domain/organization/slug-ledger.d.ts +86 -0
- package/dist/domain/organization/slug-ledger.d.ts.map +1 -0
- package/dist/domain/organization/slug-ledger.js +128 -0
- package/dist/domain/organization/slug-ledger.js.map +1 -0
- package/dist/domain/organization/steps.d.ts +12 -3
- package/dist/domain/organization/steps.d.ts.map +1 -1
- package/dist/domain/organization/steps.js +28 -6
- package/dist/domain/organization/steps.js.map +1 -1
- package/dist/domain/workflow/registry/data/model-registry.json +23 -14
- package/dist/domain/workflow/registry/model-registry-store.d.ts +14 -3
- package/dist/domain/workflow/registry/model-registry-store.d.ts.map +1 -1
- package/dist/domain/workflow/registry/model-registry-store.js +14 -3
- package/dist/domain/workflow/registry/model-registry-store.js.map +1 -1
- package/dist/extensions/gate-slots.d.ts +7 -5
- package/dist/extensions/gate-slots.d.ts.map +1 -1
- package/dist/extensions/gate-slots.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/errors.d.ts +8 -0
- package/dist/pipeline/errors.d.ts.map +1 -1
- package/dist/pipeline/errors.js +19 -0
- package/dist/pipeline/errors.js.map +1 -1
- package/dist/store/interface.d.ts +67 -0
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/organization-slug-history.d.ts +71 -0
- package/dist/store/organization-slug-history.d.ts.map +1 -0
- package/dist/store/organization-slug-history.js +88 -0
- package/dist/store/organization-slug-history.js.map +1 -0
- package/dist/store/postgres/migrations.d.ts +3 -1
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +69 -1
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts +2 -1
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +49 -0
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +3 -1
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +54 -1
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts +2 -1
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +55 -0
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/domain/agent/__tests__/store-faults.test.ts +105 -0
- package/src/domain/agent/controller.ts +9 -5
- package/src/domain/agentchannel/__tests__/store-faults.test.ts +126 -0
- package/src/domain/agentchannel/controller.ts +12 -7
- package/src/domain/agentexecution/__tests__/run-gate.test.ts +42 -1
- package/src/domain/agentexecution/__tests__/update-status.test.ts +58 -2
- package/src/domain/agentexecution/__tests__/validate-thinking-mode.test.ts +209 -61
- package/src/domain/agentexecution/controller.ts +7 -3
- package/src/domain/agentexecution/update-status.ts +43 -3
- package/src/domain/agentexecution/validate-thinking-mode.ts +102 -47
- package/src/domain/agentinstance/__tests__/store-faults.test.ts +110 -0
- package/src/domain/agentinstance/controller.ts +9 -5
- package/src/domain/agentshare/__tests__/store-faults.test.ts +101 -0
- package/src/domain/agentshare/controller.ts +9 -5
- package/src/domain/artifact/__tests__/store-faults.test.ts +117 -0
- package/src/domain/artifact/controller.ts +12 -6
- package/src/domain/environment/__tests__/store-faults.test.ts +150 -0
- package/src/domain/environment/controller.ts +8 -5
- package/src/domain/environment/steps.ts +10 -7
- package/src/domain/organization/__tests__/organization-delete.test.ts +73 -18
- package/src/domain/organization/__tests__/organization-slugs.test.ts +386 -0
- package/src/domain/organization/controller.ts +36 -14
- package/src/domain/organization/slug-ledger.ts +209 -0
- package/src/domain/organization/steps.ts +28 -6
- package/src/domain/workflow/registry/data/model-registry.json +23 -14
- package/src/domain/workflow/registry/model-registry-store.ts +16 -3
- package/src/extensions/gate-slots.ts +7 -5
- package/src/index.ts +11 -0
- package/src/pipeline/__tests__/blind-not-found.test.ts +0 -7
- package/src/pipeline/errors.ts +23 -0
- package/src/store/__tests__/organization-slug-history.test.ts +109 -0
- package/src/store/__tests__/store-contract.ts +86 -1
- package/src/store/interface.ts +70 -0
- package/src/store/organization-slug-history.ts +159 -0
- package/src/store/postgres/__tests__/migrations.test.ts +138 -2
- package/src/store/postgres/__tests__/store-contract.test.ts +1 -0
- package/src/store/postgres/migrations.ts +84 -1
- package/src/store/postgres/store.ts +84 -0
- package/src/store/sqlite/__tests__/migrations.test.ts +139 -10
- package/src/store/sqlite/migrations.ts +69 -1
- package/src/store/sqlite/store.ts +84 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.39.0",
|
|
4
4
|
"description": "The Stigmer OSS control plane as a composable library: composeServer, the extension registry and the driver seams a composition builds on. The self-contained deployable of the same server is @stigmer/server-slim.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"@connectrpc/connect-node": "^2.0.0",
|
|
64
64
|
"@kubernetes/client-node": "2.0.0",
|
|
65
65
|
"@opentelemetry/api": "^1.9.0",
|
|
66
|
-
"@stigmer/outbound": "3.
|
|
67
|
-
"@stigmer/plugin-package": "3.
|
|
68
|
-
"@stigmer/protos": "3.
|
|
69
|
-
"@stigmer/temporal-codecs": "3.
|
|
70
|
-
"@stigmer/zip-structure": "3.
|
|
66
|
+
"@stigmer/outbound": "3.39.0",
|
|
67
|
+
"@stigmer/plugin-package": "3.39.0",
|
|
68
|
+
"@stigmer/protos": "3.39.0",
|
|
69
|
+
"@stigmer/temporal-codecs": "3.39.0",
|
|
70
|
+
"@stigmer/zip-structure": "3.39.0",
|
|
71
71
|
"@temporalio/activity": "1.16.2",
|
|
72
72
|
"@temporalio/client": "1.16.2",
|
|
73
73
|
"@temporalio/common": "1.16.2",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the store-fault contract of the agent's visibility-update load: a
|
|
3
|
+
* typed ResourceNotFoundError answers NotFound with the domain's pinned copy
|
|
4
|
+
* (`agent not found: <id>`), and any other store failure is an
|
|
5
|
+
* infrastructure fault answered as a sanitized Internal, never a NotFound
|
|
6
|
+
* that tells a client the agent 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 default-instance applier is untouchable: a load that fails must stop
|
|
12
|
+
* the call before anything past it runs.
|
|
13
|
+
*
|
|
14
|
+
* Out of scope: the NotFound copy itself (wire contract) and getByAgent's
|
|
15
|
+
* empty-list answer on an agent-read fault in the channel and share domains
|
|
16
|
+
* (stigmer/stigmer#1375).
|
|
17
|
+
*/
|
|
18
|
+
import type { Client, ConnectError } from "@connectrpc/connect";
|
|
19
|
+
import { Code, createClient, createRouterTransport } from "@connectrpc/connect";
|
|
20
|
+
import { describe, expect, it } from "vitest";
|
|
21
|
+
|
|
22
|
+
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
23
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
24
|
+
|
|
25
|
+
import { createLogger } from "../../../boot/logger.js";
|
|
26
|
+
import { createApiResourceInterceptor } from "../../../pipeline/interceptors/apiresource.js";
|
|
27
|
+
import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
|
|
28
|
+
import {
|
|
29
|
+
errorOf,
|
|
30
|
+
failingStore,
|
|
31
|
+
untouchable,
|
|
32
|
+
} from "../../../pipeline/__tests__/support.js";
|
|
33
|
+
import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
|
|
34
|
+
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
35
|
+
import type { Store } from "../../../store/interface.js";
|
|
36
|
+
|
|
37
|
+
import { registerAgentServices } from "../controller.js";
|
|
38
|
+
|
|
39
|
+
const silentLogger = createLogger({
|
|
40
|
+
level: "error",
|
|
41
|
+
pretty: false,
|
|
42
|
+
write: () => {},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const AGENT_ID = "agt_storefault";
|
|
46
|
+
|
|
47
|
+
const NOT_FOUND_COPY = `agent not found: ${AGENT_ID}`;
|
|
48
|
+
const LOAD_FAULT_COPY = "failed to load agent";
|
|
49
|
+
|
|
50
|
+
const MISSING = (): Error => new ResourceNotFoundError(`agent/${AGENT_ID}`);
|
|
51
|
+
const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The registered handlers on an in-process router: the verifier chain stamps
|
|
55
|
+
* the trusted-local caller and the apiresource interceptor the kind, then
|
|
56
|
+
* Authorize and ValidateProto run and the load is the first step that reads
|
|
57
|
+
* the store.
|
|
58
|
+
*/
|
|
59
|
+
function agentCommand(store: Store): Client<typeof AgentCommandController> {
|
|
60
|
+
const transport = createRouterTransport(
|
|
61
|
+
(router) => {
|
|
62
|
+
registerAgentServices(router, {
|
|
63
|
+
store,
|
|
64
|
+
logger: silentLogger,
|
|
65
|
+
authorizer: newPermissiveSingleTeamAuthorizer(),
|
|
66
|
+
authorizationLifecycle: undefined,
|
|
67
|
+
agentInstanceApplier: untouchable("agentInstanceApplier"),
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
router: {
|
|
72
|
+
interceptors: [
|
|
73
|
+
createVerifierChainInterceptor([], [], silentLogger),
|
|
74
|
+
createApiResourceInterceptor(),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
return createClient(AgentCommandController, transport);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("updateVisibility — LoadAgentForVisibilityUpdate", () => {
|
|
83
|
+
function surfaceError(store: Store): Promise<ConnectError> {
|
|
84
|
+
return errorOf(() =>
|
|
85
|
+
agentCommand(store).updateVisibility({
|
|
86
|
+
resourceId: AGENT_ID,
|
|
87
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it("a missing agent answers NotFound with the domain's copy", async () => {
|
|
93
|
+
const error = await surfaceError(failingStore(MISSING()));
|
|
94
|
+
|
|
95
|
+
expect(error.code).toBe(Code.NotFound);
|
|
96
|
+
expect(error.rawMessage).toBe(NOT_FOUND_COPY);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("any other store failure answers a sanitized Internal", async () => {
|
|
100
|
+
const error = await surfaceError(failingStore(LOCKED()));
|
|
101
|
+
|
|
102
|
+
expect(error.code).toBe(Code.Internal);
|
|
103
|
+
expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* agent to serve: a session with no agent runs the built-in assistant.
|
|
9
9
|
*
|
|
10
10
|
* Pipeline per RPC mirrors the Go step chains character-for-character.
|
|
11
|
-
* Proven by agent.conformance.test.ts (CONFORMANCE_TARGET=local)
|
|
12
|
-
* __tests__/agent.test.ts.
|
|
11
|
+
* Proven by agent.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
12
|
+
* __tests__/agent.test.ts and __tests__/store-faults.test.ts.
|
|
13
13
|
*
|
|
14
14
|
* Versus Stigmer Cloud, OSS excludes the Authorize, CreateIamPolicies, and
|
|
15
15
|
* Publish steps (no multi-tenant auth, IAM/FGA, or event publishing here).
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
newValidateVisibilityStep,
|
|
92
92
|
newValidateVisibilityUpdateStep,
|
|
93
93
|
} from "../../pipeline/steps/validate-visibility.js";
|
|
94
|
+
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
94
95
|
import type { Store } from "../../store/interface.js";
|
|
95
96
|
import { agentSearchExtractor } from "./search-extractor.js";
|
|
96
97
|
import {
|
|
@@ -382,7 +383,7 @@ async function updateVisibility(
|
|
|
382
383
|
return reqCtx.get(UPDATE_VISIBILITY_AGENT_KEY) as Agent;
|
|
383
384
|
}
|
|
384
385
|
|
|
385
|
-
/** Loads the agent by resource_id;
|
|
386
|
+
/** Loads the agent by resource_id; a missing one answers NotFound, a store fault Internal. */
|
|
386
387
|
function newLoadAgentForVisibilityUpdateStep(
|
|
387
388
|
store: Store,
|
|
388
389
|
): PipelineStep<UpdateVisibilityDesc> {
|
|
@@ -397,8 +398,11 @@ function newLoadAgentForVisibilityUpdateStep(
|
|
|
397
398
|
input.resourceId,
|
|
398
399
|
AgentSchema,
|
|
399
400
|
);
|
|
400
|
-
} catch {
|
|
401
|
-
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof ResourceNotFoundError) {
|
|
403
|
+
throw notFoundError("agent", input.resourceId);
|
|
404
|
+
}
|
|
405
|
+
throw internalError(error, "failed to load agent");
|
|
402
406
|
}
|
|
403
407
|
ctx.set(UPDATE_VISIBILITY_AGENT_KEY, agent);
|
|
404
408
|
},
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the store-fault contract of the install lane's channel load, which
|
|
3
|
+
* initiateInstall and completeInstall share: a typed ResourceNotFoundError
|
|
4
|
+
* answers NotFound with the domain's pinned copy
|
|
5
|
+
* (`AgentChannel not found: <id>`), and any other store failure is an
|
|
6
|
+
* infrastructure fault answered as a sanitized Internal, never a NotFound
|
|
7
|
+
* that tells a client the channel does not exist (stigmer/stigmer#1345).
|
|
8
|
+
*
|
|
9
|
+
* Both surfaces are reached through the registered handler on an in-process
|
|
10
|
+
* router. The composed suites reach only a real store, which cannot fail
|
|
11
|
+
* selectively, so each runs here against a store whose read throws. The
|
|
12
|
+
* channel runtime and the model registry are untouchable: the load comes
|
|
13
|
+
* before authorization and before the runtime, so a load that fails must
|
|
14
|
+
* stop the call before either.
|
|
15
|
+
*
|
|
16
|
+
* Out of scope: the NotFound copy itself (wire contract, pinned by
|
|
17
|
+
* agentchannel.test.ts and the agentchannel conformance suite) and
|
|
18
|
+
* getByAgent's empty-list answer on an agent-read fault
|
|
19
|
+
* (stigmer/stigmer#1375).
|
|
20
|
+
*/
|
|
21
|
+
import type { Client, ConnectError } from "@connectrpc/connect";
|
|
22
|
+
import { Code, createClient, createRouterTransport } from "@connectrpc/connect";
|
|
23
|
+
import { describe, expect, it } from "vitest";
|
|
24
|
+
|
|
25
|
+
import { AgentChannelCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/command_pb";
|
|
26
|
+
|
|
27
|
+
import { createLogger } from "../../../boot/logger.js";
|
|
28
|
+
import { createApiResourceInterceptor } from "../../../pipeline/interceptors/apiresource.js";
|
|
29
|
+
import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
|
|
30
|
+
import {
|
|
31
|
+
errorOf,
|
|
32
|
+
failingStore,
|
|
33
|
+
untouchable,
|
|
34
|
+
} from "../../../pipeline/__tests__/support.js";
|
|
35
|
+
import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
|
|
36
|
+
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
37
|
+
import type { Store } from "../../../store/interface.js";
|
|
38
|
+
|
|
39
|
+
import type { ChannelRuntime } from "../channel-runtime.js";
|
|
40
|
+
import { registerAgentChannelServices } from "../controller.js";
|
|
41
|
+
|
|
42
|
+
const silentLogger = createLogger({
|
|
43
|
+
level: "error",
|
|
44
|
+
pretty: false,
|
|
45
|
+
write: () => {},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const CHANNEL_ID = "ach_storefault";
|
|
49
|
+
|
|
50
|
+
const NOT_FOUND_COPY = `AgentChannel not found: ${CHANNEL_ID}`;
|
|
51
|
+
const LOAD_FAULT_COPY = "failed to load agent channel";
|
|
52
|
+
|
|
53
|
+
const MISSING = (): Error =>
|
|
54
|
+
new ResourceNotFoundError(`agent_channel/${CHANNEL_ID}`);
|
|
55
|
+
const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
|
|
56
|
+
|
|
57
|
+
type ChannelCommand = Client<typeof AgentChannelCommandController>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The registered handlers on an in-process router: the verifier chain stamps
|
|
61
|
+
* the trusted-local caller and the apiresource interceptor the kind the
|
|
62
|
+
* install load reads; the load is the first thing either handler does.
|
|
63
|
+
*/
|
|
64
|
+
function channelCommand(store: Store): ChannelCommand {
|
|
65
|
+
const transport = createRouterTransport(
|
|
66
|
+
(router) => {
|
|
67
|
+
registerAgentChannelServices(router, {
|
|
68
|
+
store,
|
|
69
|
+
logger: silentLogger,
|
|
70
|
+
authorizer: newPermissiveSingleTeamAuthorizer(),
|
|
71
|
+
authorizationLifecycle: undefined,
|
|
72
|
+
modelRegistry: untouchable("modelRegistry"),
|
|
73
|
+
channelRuntime: untouchable<ChannelRuntime>("channelRuntime"),
|
|
74
|
+
listReadScope: undefined,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
router: {
|
|
79
|
+
interceptors: [
|
|
80
|
+
createVerifierChainInterceptor([], [], silentLogger),
|
|
81
|
+
createApiResourceInterceptor(),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
return createClient(AgentChannelCommandController, transport);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Each surface with the input its proto requires. */
|
|
90
|
+
const SURFACES: ReadonlyArray<
|
|
91
|
+
readonly [string, (command: ChannelCommand) => Promise<unknown>]
|
|
92
|
+
> = [
|
|
93
|
+
[
|
|
94
|
+
"initiateInstall — the install load",
|
|
95
|
+
(command) => command.initiateInstall({ resourceId: CHANNEL_ID }),
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"completeInstall — the install load",
|
|
99
|
+
(command) =>
|
|
100
|
+
command.completeInstall({
|
|
101
|
+
resourceId: CHANNEL_ID,
|
|
102
|
+
state: "install-state",
|
|
103
|
+
code: "provider-code",
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
describe.each(SURFACES)("%s", (_name, call) => {
|
|
109
|
+
function surfaceError(store: Store): Promise<ConnectError> {
|
|
110
|
+
return errorOf(() => call(channelCommand(store)));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
it("a missing channel answers NotFound with the domain's copy", async () => {
|
|
114
|
+
const error = await surfaceError(failingStore(MISSING()));
|
|
115
|
+
|
|
116
|
+
expect(error.code).toBe(Code.NotFound);
|
|
117
|
+
expect(error.rawMessage).toBe(NOT_FOUND_COPY);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("any other store failure answers a sanitized Internal", async () => {
|
|
121
|
+
const error = await surfaceError(failingStore(LOCKED()));
|
|
122
|
+
|
|
123
|
+
expect(error.code).toBe(Code.Internal);
|
|
124
|
+
expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* (only same-org shares are) — a dangling channel is tolerated because
|
|
24
24
|
* OSS has no serving runtime and cloud fails closed elsewhere.
|
|
25
25
|
*
|
|
26
|
-
* Proven by agentchannel.conformance.test.ts (CONFORMANCE_TARGET=local)
|
|
27
|
-
* and __tests__/
|
|
26
|
+
* Proven by agentchannel.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
27
|
+
* __tests__/agentchannel.test.ts and __tests__/store-faults.test.ts.
|
|
28
28
|
*/
|
|
29
29
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
30
30
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
@@ -108,6 +108,7 @@ import { newPersistStep } from "../../pipeline/steps/persist.js";
|
|
|
108
108
|
import { newResolveSlugStep } from "../../pipeline/steps/slug.js";
|
|
109
109
|
import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
|
|
110
110
|
import { newValidateVisibilityStep } from "../../pipeline/steps/validate-visibility.js";
|
|
111
|
+
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
111
112
|
import type { Store } from "../../store/interface.js";
|
|
112
113
|
import type { ModelCatalogProvider } from "../workflow/registry/model-catalog-provider.js";
|
|
113
114
|
import type { ChannelRuntime } from "./channel-runtime.js";
|
|
@@ -395,9 +396,10 @@ async function completeInstall(
|
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
/**
|
|
398
|
-
* Loads the install target — cloud's LoadChannel NOT_FOUND, verbatim
|
|
399
|
-
*
|
|
400
|
-
* second load (the
|
|
399
|
+
* Loads the install target — cloud's LoadChannel NOT_FOUND, verbatim, for
|
|
400
|
+
* a missing channel; a store fault answers Internal. Returns the loaded
|
|
401
|
+
* channel so a composed runtime receives it without a second load (the
|
|
402
|
+
* load-then-X contract stays OSS-owned).
|
|
401
403
|
*/
|
|
402
404
|
async function loadChannelForInstall(
|
|
403
405
|
deps: AgentChannelControllerDeps,
|
|
@@ -410,8 +412,11 @@ async function loadChannelForInstall(
|
|
|
410
412
|
resourceId,
|
|
411
413
|
AgentChannelSchema,
|
|
412
414
|
);
|
|
413
|
-
} catch {
|
|
414
|
-
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (error instanceof ResourceNotFoundError) {
|
|
417
|
+
throw notFoundError("AgentChannel", resourceId);
|
|
418
|
+
}
|
|
419
|
+
throw internalError(error, "failed to load agent channel");
|
|
415
420
|
}
|
|
416
421
|
}
|
|
417
422
|
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* denial leaves no execution row AND creates no default instance — the
|
|
8
8
|
* "no side effect precedes the check" property; (3) the gate precedes the
|
|
9
9
|
* engine gate: an ALLOWED create on this engineless server answers the
|
|
10
|
-
* engine's UNAVAILABLE, a DENIED one PERMISSION_DENIED
|
|
10
|
+
* engine's UNAVAILABLE, a DENIED one PERMISSION_DENIED; (4) the
|
|
11
|
+
* thinking-mode validation, which may read the stored session, runs behind
|
|
12
|
+
* the gate (stigmer/stigmer#1280): a DENIED create with an invalid thinking
|
|
13
|
+
* mode answers PERMISSION_DENIED, an ALLOWED one INVALID_ARGUMENT. The authorizer
|
|
11
14
|
* under test answers only run-gate checks (runGateOnlyAuthorizer), so every
|
|
12
15
|
* refusal here is the run gate's and nobody else's.
|
|
13
16
|
*/
|
|
@@ -25,6 +28,7 @@ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb"
|
|
|
25
28
|
import { AgentExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/command_pb";
|
|
26
29
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
27
30
|
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
31
|
+
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
28
32
|
|
|
29
33
|
import { loadConfig } from "../../../boot/config.js";
|
|
30
34
|
import { composeServer } from "../../../boot/compose.js";
|
|
@@ -134,6 +138,7 @@ function createInput(spec: {
|
|
|
134
138
|
agentId?: string;
|
|
135
139
|
sessionId?: string;
|
|
136
140
|
sessionSpec?: { agentInstanceId: string };
|
|
141
|
+
executionConfig?: { modelName?: string; thinkingMode?: ThinkingMode };
|
|
137
142
|
}) {
|
|
138
143
|
return {
|
|
139
144
|
apiVersion: API_VERSION,
|
|
@@ -242,4 +247,40 @@ describe("agent-execution-create run gate (composed server)", () => {
|
|
|
242
247
|
expect(err.rawMessage).toBe(ENGINE_UNAVAILABLE_MESSAGE);
|
|
243
248
|
expect(gate.runGateChecks.map((c) => c.resourceId)).toEqual([agentId]);
|
|
244
249
|
});
|
|
250
|
+
|
|
251
|
+
it("thinking-mode validation runs behind the gate: a denied caller learns nothing about the session", async () => {
|
|
252
|
+
decision = { kind: "deny", reason: "" };
|
|
253
|
+
gate.runGateChecks.length = 0;
|
|
254
|
+
|
|
255
|
+
const err = await captureError(() =>
|
|
256
|
+
command.create(
|
|
257
|
+
createInput({
|
|
258
|
+
sessionId: "ses_01someoneelses",
|
|
259
|
+
executionConfig: { modelName: "composer-2.5", thinkingMode: ThinkingMode.ENABLED },
|
|
260
|
+
}),
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(err.code).toBe(Code.PermissionDenied);
|
|
265
|
+
expect(err.rawMessage).toBe(addExecutionToSessionDeniedMessage("ses_01someoneelses"));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("an allowed create with an invalid thinking mode is refused by the validation, before the engine gate", async () => {
|
|
269
|
+
decision = { kind: "allow" };
|
|
270
|
+
const agentId = await createAgentWithoutDefaultInstance("Thinking agent");
|
|
271
|
+
const instancesBefore = await count(ApiResourceKind.agent_instance);
|
|
272
|
+
|
|
273
|
+
const err = await captureError(() =>
|
|
274
|
+
command.create(
|
|
275
|
+
createInput({
|
|
276
|
+
agentId,
|
|
277
|
+
executionConfig: { modelName: "composer-2.5", thinkingMode: ThinkingMode.ENABLED },
|
|
278
|
+
}),
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(err.code).toBe(Code.InvalidArgument);
|
|
283
|
+
expect(err.rawMessage).toContain("no thinking capability");
|
|
284
|
+
expect(await count(ApiResourceKind.agent_instance)).toBe(instancesBefore);
|
|
285
|
+
});
|
|
245
286
|
});
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* Also pins the platform-row rules (platform-rows.ts, stigmer#980): a
|
|
9
9
|
* platform marker write is appended to the transcript and never replaces
|
|
10
10
|
* it, idempotently, and a PAUSED or CANCELLED execution's stop row stays
|
|
11
|
-
* last across the runner's late writes, whatever order they land in
|
|
11
|
+
* last across the runner's late writes, whatever order they land in; and a
|
|
12
|
+
* paused execution's phase moves only on the platform's own write
|
|
13
|
+
* (stigmer#1370), so the runner's straggler streaming cannot un-pause it.
|
|
12
14
|
* These exercise applyUpdateStatusMerge directly on a clone — mirroring
|
|
13
15
|
* the freshly-loaded resource the merge mutates in place inside the
|
|
14
16
|
* updateResource write lock.
|
|
@@ -37,6 +39,7 @@ import { createLogger } from "../../../boot/logger.js";
|
|
|
37
39
|
import { findToolCallInExecution } from "../submit-approval.js";
|
|
38
40
|
import { CANCEL_STOP_ROW, PAUSE_STOP_ROW } from "../platform-rows.js";
|
|
39
41
|
import { applyUpdateStatusMerge } from "../update-status.js";
|
|
42
|
+
import type { StatusWriter } from "../update-status.js";
|
|
40
43
|
|
|
41
44
|
const silentLogger = createLogger({
|
|
42
45
|
level: "error",
|
|
@@ -51,13 +54,14 @@ const silentLogger = createLogger({
|
|
|
51
54
|
function runBuildStep(
|
|
52
55
|
existing: AgentExecution,
|
|
53
56
|
incoming: MessageInitShape<typeof AgentExecutionStatusSchema>,
|
|
57
|
+
writer: StatusWriter = "wire",
|
|
54
58
|
): AgentExecution {
|
|
55
59
|
const input = create(AgentExecutionUpdateStatusInputSchema, {
|
|
56
60
|
executionId: existing.metadata?.id ?? "",
|
|
57
61
|
status: incoming,
|
|
58
62
|
});
|
|
59
63
|
const merged = clone(AgentExecutionSchema, existing);
|
|
60
|
-
applyUpdateStatusMerge(merged, input, silentLogger);
|
|
64
|
+
applyUpdateStatusMerge(merged, input, silentLogger, writer);
|
|
61
65
|
return merged;
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -584,3 +588,55 @@ describe("platform rows (stigmer#980)", () => {
|
|
|
584
588
|
});
|
|
585
589
|
});
|
|
586
590
|
});
|
|
591
|
+
|
|
592
|
+
describe("a paused execution stays paused until Resume (stigmer#1370)", () => {
|
|
593
|
+
function pausedWith(...contents: string[]): AgentExecution {
|
|
594
|
+
return create(AgentExecutionSchema, {
|
|
595
|
+
metadata: { id: "exec-paused", name: "exec-paused" },
|
|
596
|
+
spec: {},
|
|
597
|
+
status: {
|
|
598
|
+
phase: ExecutionPhase.EXECUTION_PAUSED,
|
|
599
|
+
messages: [
|
|
600
|
+
...contents.map((content) => ({ type: MessageType.MESSAGE_AI, content })),
|
|
601
|
+
{ type: MessageType.MESSAGE_SYSTEM, content: PAUSE_STOP_ROW },
|
|
602
|
+
],
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const contentsOf = (e: AgentExecution) => (e.status?.messages ?? []).map((m) => m.content);
|
|
607
|
+
|
|
608
|
+
it("keeps PAUSED across the runner's straggler streaming write, and merges its text beneath the pause row", () => {
|
|
609
|
+
// The Pause RPC and the workflow's row landed; the runner learns of the
|
|
610
|
+
// pause on a later heartbeat and keeps streaming IN_PROGRESS writes that
|
|
611
|
+
// carry its growing transcript without the row.
|
|
612
|
+
const merged = runBuildStep(pausedWith("The press, part"), {
|
|
613
|
+
phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
614
|
+
messages: [{ type: MessageType.MESSAGE_AI, content: "The press, part one and two" }],
|
|
615
|
+
});
|
|
616
|
+
expect(merged.status?.phase, "a wire write cannot un-pause").toBe(ExecutionPhase.EXECUTION_PAUSED);
|
|
617
|
+
expect(contentsOf(merged)).toEqual(["The press, part one and two", PAUSE_STOP_ROW]);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it("keeps PAUSED against a wire phase-only IN_PROGRESS write", () => {
|
|
621
|
+
const merged = runBuildStep(pausedWith("a1"), { phase: ExecutionPhase.EXECUTION_IN_PROGRESS });
|
|
622
|
+
expect(merged.status?.phase).toBe(ExecutionPhase.EXECUTION_PAUSED);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("lets the platform's own write move a paused execution (the workflow's resume heal)", () => {
|
|
626
|
+
const merged = runBuildStep(
|
|
627
|
+
pausedWith("a1"),
|
|
628
|
+
{ phase: ExecutionPhase.EXECUTION_IN_PROGRESS },
|
|
629
|
+
"platform",
|
|
630
|
+
);
|
|
631
|
+
expect(merged.status?.phase).toBe(ExecutionPhase.EXECUTION_IN_PROGRESS);
|
|
632
|
+
expect(contentsOf(merged)).toEqual(["a1", PAUSE_STOP_ROW]);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("leaves a wire write on a running execution as it was (the latch is PAUSED's alone)", () => {
|
|
636
|
+
const merged = runBuildStep(existingWith(ExecutionPhase.EXECUTION_IN_PROGRESS, "m1"), {
|
|
637
|
+
phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
|
|
638
|
+
messages: [{ content: "m1" }],
|
|
639
|
+
});
|
|
640
|
+
expect(merged.status?.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
|
|
641
|
+
});
|
|
642
|
+
});
|