@stigmer/server 3.20.0 → 3.22.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/authorization/evaluator.d.ts +7 -1
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +19 -11
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +2 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +0 -1
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +0 -2
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +0 -6
- package/dist/boot/inprocess.js.map +1 -1
- 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 +3 -17
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agent/steps.d.ts +0 -1
- package/dist/domain/agent/steps.d.ts.map +1 -1
- package/dist/domain/agent/steps.js +1 -40
- package/dist/domain/agent/steps.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +6 -1
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentchannel/steps.d.ts +19 -0
- package/dist/domain/agentchannel/steps.d.ts.map +1 -1
- package/dist/domain/agentchannel/steps.js +32 -0
- package/dist/domain/agentchannel/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -9
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
- package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
- package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +16 -31
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +36 -102
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +7 -3
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
- package/dist/domain/agentexecution/run-target.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +5 -5
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +6 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +43 -8
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +71 -8
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +7 -1
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +58 -0
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +109 -4
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/artifact/controller.js +21 -25
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js +2 -0
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +2 -0
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/environment/personal.d.ts +42 -0
- package/dist/domain/environment/personal.d.ts.map +1 -0
- package/dist/domain/environment/personal.js +96 -0
- package/dist/domain/environment/personal.js.map +1 -0
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +2 -0
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/executioncontext/steps.d.ts.map +1 -1
- package/dist/domain/executioncontext/steps.js +2 -1
- package/dist/domain/executioncontext/steps.js.map +1 -1
- package/dist/domain/mcpserver/connect.d.ts +0 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +24 -79
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +2 -0
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +8 -7
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +6 -1
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +23 -0
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +35 -0
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/oauthapp/controller.d.ts.map +1 -1
- package/dist/domain/oauthapp/controller.js +2 -0
- package/dist/domain/oauthapp/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +5 -24
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +2 -2
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +7 -1
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/schedule/steps.d.ts +19 -0
- package/dist/domain/schedule/steps.d.ts.map +1 -1
- package/dist/domain/schedule/steps.js +32 -0
- package/dist/domain/schedule/steps.js.map +1 -1
- package/dist/domain/session/controller.d.ts +0 -7
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +11 -16
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +9 -8
- package/dist/domain/session/run-target.d.ts.map +1 -1
- package/dist/domain/session/run-target.js.map +1 -1
- package/dist/domain/session/steps.d.ts +8 -36
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +27 -172
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +8 -17
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +30 -107
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/version-resolution.d.ts +5 -0
- package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
- package/dist/domain/workflow/version-resolution.js +113 -0
- package/dist/domain/workflow/version-resolution.js.map +1 -0
- package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
- package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/workflowexecution/create-steps.js +4 -3
- package/dist/domain/workflowexecution/create-steps.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +6 -1
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +20 -0
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +39 -0
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +19 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +22 -3
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +41 -7
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +13 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +2 -2
- package/dist/pipeline/apiresource-labels.js +2 -2
- package/dist/pipeline/errors.d.ts +2 -2
- package/dist/pipeline/errors.js +2 -2
- package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
- package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
- package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
- package/dist/pipeline/steps/authorize.d.ts +27 -0
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +49 -10
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +2 -1
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +12 -12
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts +9 -5
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js +11 -15
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/version-history.d.ts +11 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -1
- package/dist/pipeline/steps/version-history.js +26 -0
- package/dist/pipeline/steps/version-history.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
- package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
- package/src/authorization/evaluator.ts +7 -0
- package/src/authorization/list-read-scope.ts +22 -11
- package/src/authorization/model/__tests__/registry.test.ts +2 -0
- package/src/authorization/model/organization.ts +2 -0
- package/src/boot/compose.ts +0 -1
- package/src/boot/inprocess.ts +0 -9
- package/src/domain/agent/__tests__/agent.test.ts +0 -68
- package/src/domain/agent/controller.ts +11 -36
- package/src/domain/agent/steps.ts +1 -60
- package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/agentchannel/controller.ts +19 -0
- package/src/domain/agentchannel/steps.ts +40 -0
- package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
- package/src/domain/agentexecution/controller.ts +6 -10
- package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
- package/src/domain/agentexecution/create-steps.ts +38 -138
- package/src/domain/agentexecution/run-target.ts +7 -3
- package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
- package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
- package/src/domain/agentinstance/controller.ts +24 -5
- package/src/domain/agentinstance/steps.ts +82 -8
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
- package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
- package/src/domain/agentshare/controller.ts +21 -0
- package/src/domain/agentshare/steps.ts +181 -15
- package/src/domain/artifact/controller.ts +18 -22
- package/src/domain/channelapp/controller.ts +10 -0
- package/src/domain/environment/__tests__/personal.test.ts +143 -0
- package/src/domain/environment/controller.ts +10 -0
- package/src/domain/environment/personal.ts +141 -0
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
- package/src/domain/executioncontext/controller.ts +10 -0
- package/src/domain/executioncontext/steps.ts +2 -1
- package/src/domain/mcpserver/connect.ts +32 -92
- package/src/domain/mcpserver/controller.ts +13 -1
- package/src/domain/mcpserver/start-connect.ts +17 -11
- package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
- package/src/domain/memory/controller.ts +11 -0
- package/src/domain/memory/steps.ts +41 -0
- package/src/domain/oauthapp/controller.ts +10 -0
- package/src/domain/plugin/controller.ts +22 -36
- package/src/domain/plugin/overlay/sanitize.ts +2 -2
- package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/schedule/controller.ts +20 -0
- package/src/domain/schedule/steps.ts +45 -2
- package/src/domain/session/__tests__/run-gate.test.ts +19 -52
- package/src/domain/session/__tests__/session.test.ts +27 -203
- package/src/domain/session/controller.ts +10 -30
- package/src/domain/session/run-target.ts +9 -8
- package/src/domain/session/steps.ts +33 -267
- package/src/domain/skill/controller.ts +24 -23
- package/src/domain/workflow/controller.ts +58 -145
- package/src/domain/workflow/version-resolution.ts +166 -0
- package/src/domain/workflowexecution/create-steps.ts +4 -3
- package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
- package/src/domain/workflowinstance/controller.ts +19 -0
- package/src/domain/workflowinstance/steps.ts +49 -1
- package/src/extensions/__tests__/composed-support.ts +38 -4
- package/src/extensions/__tests__/identity.test.ts +44 -1
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
- package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
- package/src/extensions/identity.ts +23 -5
- package/src/extensions/list-read-scope.ts +47 -7
- package/src/pipeline/apiresource-labels.ts +2 -2
- package/src/pipeline/errors.ts +2 -2
- package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
- package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
- package/src/pipeline/steps/authorize.ts +70 -13
- package/src/pipeline/steps/guard-memory-capture.ts +2 -1
- package/src/pipeline/steps/guard-reserved-labels.ts +12 -14
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/version-history.ts +34 -0
- package/dist/domain/agent/defaultagent.d.ts +0 -37
- package/dist/domain/agent/defaultagent.d.ts.map +0 -1
- package/dist/domain/agent/defaultagent.js +0 -104
- package/dist/domain/agent/defaultagent.js.map +0 -1
- package/src/domain/agent/defaultagent.ts +0 -135
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* compose_declared_preferences_step.go, and
|
|
4
4
|
* compose_recalled_memories_step.go. The chain itself is assembled in
|
|
5
5
|
* controller.ts, mirroring Go buildCreatePipeline order exactly.
|
|
6
|
+
*
|
|
7
|
+
* An execution names its target one of three ways (session_id, agent_id,
|
|
8
|
+
* session_spec.agent_instance_id) or not at all: the all-empty shape is the
|
|
9
|
+
* built-in assistant (agentexecution/v1/spec.proto), for which
|
|
10
|
+
* CreateSessionIfNeeded creates a session with no agent and the runner
|
|
11
|
+
* resolves an agent-less blueprint. Nothing here resolves a stored default
|
|
12
|
+
* agent into that shape.
|
|
6
13
|
*/
|
|
7
14
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
8
15
|
|
|
@@ -44,15 +51,10 @@ import {
|
|
|
44
51
|
notFoundError,
|
|
45
52
|
rethrownStatusError,
|
|
46
53
|
} from "../../pipeline/errors.js";
|
|
47
|
-
import { ConnectError
|
|
54
|
+
import { ConnectError } from "@connectrpc/connect";
|
|
48
55
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
49
56
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
50
57
|
import type { Store } from "../../store/interface.js";
|
|
51
|
-
import {
|
|
52
|
-
DefaultAgentNotConfiguredError,
|
|
53
|
-
DefaultAgentNotPublicError,
|
|
54
|
-
findDefaultAgent,
|
|
55
|
-
} from "../agent/defaultagent.js";
|
|
56
58
|
import { buildDefaultInstanceRequest } from "../agentinstance/defaultinstance.js";
|
|
57
59
|
|
|
58
60
|
import type { AgentExecutionStatusObserver } from "../../extensions/status-hooks.js";
|
|
@@ -94,120 +96,6 @@ export interface SessionCreator {
|
|
|
94
96
|
}
|
|
95
97
|
export type SessionCreatorProvider = () => SessionCreator;
|
|
96
98
|
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
// ResolveDefaultAgent — create.go resolveDefaultAgentStep.
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Resolves the platform's public default agent when neither session_id
|
|
103
|
-
* nor agent_id (nor a session_spec instance) is provided — the
|
|
104
|
-
* session-first UX, a VALID request shape, not an input error. Resolution
|
|
105
|
-
* (candidate set, visibility preference, deterministic incumbent-wins
|
|
106
|
-
* tie-break) is owned by the defaultagent module, shared with the
|
|
107
|
-
* Agent.GetDefault RPC and session create.
|
|
108
|
-
*/
|
|
109
|
-
export function newResolveDefaultAgentStep(
|
|
110
|
-
store: Store,
|
|
111
|
-
logger: Logger,
|
|
112
|
-
): PipelineStep<CreateDesc> {
|
|
113
|
-
return {
|
|
114
|
-
name: "ResolveDefaultAgent",
|
|
115
|
-
async execute(ctx) {
|
|
116
|
-
const spec = ctx.input.spec;
|
|
117
|
-
if (
|
|
118
|
-
(spec?.sessionId ?? "") !== "" ||
|
|
119
|
-
(spec?.agentId ?? "") !== "" ||
|
|
120
|
-
(spec?.sessionSpec?.agentInstanceId ?? "") !== ""
|
|
121
|
-
) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
logger.info(
|
|
126
|
-
"Neither session_id nor agent_id provided, resolving platform default agent",
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
let defaultAgent: Agent;
|
|
130
|
-
try {
|
|
131
|
-
defaultAgent = await findDefaultAgent(store, logger);
|
|
132
|
-
} catch (error) {
|
|
133
|
-
if (error instanceof DefaultAgentNotConfiguredError) {
|
|
134
|
-
// Caller-actionable message: the create caller can fix this by
|
|
135
|
-
// supplying a reference — deliberately different from the
|
|
136
|
-
// Agent.GetDefault RPC's message, whose caller cannot.
|
|
137
|
-
throw new ConnectError(
|
|
138
|
-
"No default agent is configured on this platform. Provide session_id or agent_id explicitly, or seed an agent labeled stigmer.ai/default-agent=true with visibility_public",
|
|
139
|
-
Code.NotFound,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
if (error instanceof DefaultAgentNotPublicError) {
|
|
143
|
-
throw failedPreconditionError(
|
|
144
|
-
"Default agent exists but is not visibility_public",
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
// Store/decode failure — an internal fault, not "no default
|
|
148
|
-
// agent". The sanitized wire copy keeps the cause off the wire
|
|
149
|
-
// (stigmer/stigmer#478).
|
|
150
|
-
throw internalError(
|
|
151
|
-
error,
|
|
152
|
-
"failed to resolve the platform default agent",
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const resolvedId = defaultAgent.metadata?.id ?? "";
|
|
157
|
-
logger.info("Resolved platform default agent", {
|
|
158
|
-
agentId: resolvedId,
|
|
159
|
-
agentName: defaultAgent.metadata?.name ?? "",
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Set agent_id on newState (not input): later steps and Persist
|
|
163
|
-
// operate on newState.
|
|
164
|
-
const newState = ctx.newState;
|
|
165
|
-
const newSpec = (newState.spec ??= create(AgentExecutionSpecSchema));
|
|
166
|
-
newSpec.agentId = resolvedId;
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
// EnsureSessionOrAgentResolved — the invariant guard.
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Asserts the post-condition that a session, agent, or
|
|
177
|
-
* embedded-session-spec instance reference has been resolved. An
|
|
178
|
-
* invariant guard, NOT input validation: ResolveDefaultAgent runs first
|
|
179
|
-
* and guarantees one of the three or an error, so reaching this step with
|
|
180
|
-
* none set is a server-side programming error — hence Internal, not
|
|
181
|
-
* InvalidArgument. Deliberately diverges from WorkflowExecution's
|
|
182
|
-
* validateWorkflowOrInstanceStep (InvalidArgument), whose check is
|
|
183
|
-
* genuinely reachable (issue #196) — do not "harmonize" the two.
|
|
184
|
-
*/
|
|
185
|
-
export function newEnsureSessionOrAgentResolvedStep(
|
|
186
|
-
logger: Logger,
|
|
187
|
-
): PipelineStep<CreateDesc> {
|
|
188
|
-
return {
|
|
189
|
-
name: "EnsureSessionOrAgentResolved",
|
|
190
|
-
execute(ctx) {
|
|
191
|
-
const spec = ctx.newState.spec;
|
|
192
|
-
const hasSessionId = (spec?.sessionId ?? "") !== "";
|
|
193
|
-
const hasAgentId = (spec?.agentId ?? "") !== "";
|
|
194
|
-
const hasSpecInstanceId =
|
|
195
|
-
(spec?.sessionSpec?.agentInstanceId ?? "") !== "";
|
|
196
|
-
if (!hasSessionId && !hasAgentId && !hasSpecInstanceId) {
|
|
197
|
-
logger.error(
|
|
198
|
-
"Invariant violated: no session, agent, or session_spec instance reference resolved after ResolveDefaultAgent",
|
|
199
|
-
);
|
|
200
|
-
throw internalError(
|
|
201
|
-
new Error(
|
|
202
|
-
"neither session_id, agent_id, nor session_spec.agent_instance_id set after ResolveDefaultAgent",
|
|
203
|
-
),
|
|
204
|
-
"execution target not resolved",
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
99
|
// ---------------------------------------------------------------------------
|
|
212
100
|
// EnsureEngineAvailable lives in engine.ts (Phase 1); re-exported by the
|
|
213
101
|
// controller for chain assembly.
|
|
@@ -233,11 +121,13 @@ export type ExecutionAgentInstanceCreatorProvider =
|
|
|
233
121
|
|
|
234
122
|
/**
|
|
235
123
|
* Ensures the referenced agent has a default instance: skips when a
|
|
236
|
-
* session or explicit session_spec instance names the target
|
|
237
|
-
* agent
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
124
|
+
* session or explicit session_spec instance names the target, and when no
|
|
125
|
+
* agent is named at all (the built-in assistant: there is no agent whose
|
|
126
|
+
* instance could be created); otherwise loads the agent via the in-process
|
|
127
|
+
* client, creates the default instance when the status lacks one, saves
|
|
128
|
+
* the agent status DIRECTLY to the store (matching Go/Java: repo save, not
|
|
129
|
+
* the Update pipeline), and stores the instance id in the context for the
|
|
130
|
+
* next step.
|
|
241
131
|
*/
|
|
242
132
|
export function newCreateDefaultInstanceIfNeededStep(deps: {
|
|
243
133
|
store: Store;
|
|
@@ -261,8 +151,7 @@ export function newCreateDefaultInstanceIfNeededStep(deps: {
|
|
|
261
151
|
return;
|
|
262
152
|
}
|
|
263
153
|
// An explicit session_spec instance fully specifies the target — no
|
|
264
|
-
// agent load or default-instance creation needed
|
|
265
|
-
// lookup would stamp misleading metadata).
|
|
154
|
+
// agent load or default-instance creation needed.
|
|
266
155
|
const specInstanceId = execution.spec?.sessionSpec?.agentInstanceId ?? "";
|
|
267
156
|
if (specInstanceId !== "") {
|
|
268
157
|
deps.logger.debug(
|
|
@@ -271,6 +160,14 @@ export function newCreateDefaultInstanceIfNeededStep(deps: {
|
|
|
271
160
|
);
|
|
272
161
|
return;
|
|
273
162
|
}
|
|
163
|
+
// No agent named: the built-in assistant runs in a session with no
|
|
164
|
+
// instance, so there is nothing to load or create here.
|
|
165
|
+
if (agentId === "") {
|
|
166
|
+
deps.logger.debug(
|
|
167
|
+
"No agent named, the built-in assistant runs; skipping default instance check",
|
|
168
|
+
);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
274
171
|
|
|
275
172
|
// 1. Load agent via in-process gRPC (single source of truth). Go
|
|
276
173
|
// returns the client error AS-IS here (create.go: "already a gRPC
|
|
@@ -385,7 +282,9 @@ export function newCreateDefaultInstanceIfNeededStep(deps: {
|
|
|
385
282
|
* bootstrap, stigmer/stigmer#249) is CLONED and forwarded so the session
|
|
386
283
|
* carries workspace_entries, harness, execution_target, MCP servers, and
|
|
387
284
|
* skills from a single create call; defaults fill in the instance (when
|
|
388
|
-
* the spec names none
|
|
285
|
+
* the spec names none and an agent's default instance was resolved; an
|
|
286
|
+
* empty id here and there is the built-in assistant) and the subject
|
|
287
|
+
* sentinel (when empty).
|
|
389
288
|
*/
|
|
390
289
|
export function buildAutoCreateSessionSpec(
|
|
391
290
|
callerSpec: SessionSpec | undefined,
|
|
@@ -407,11 +306,12 @@ export function buildAutoCreateSessionSpec(
|
|
|
407
306
|
/**
|
|
408
307
|
* Auto-creates the session when session_id is absent: forwards the
|
|
409
308
|
* caller's session_spec, fills the instance from the previous step's
|
|
410
|
-
* context key when
|
|
411
|
-
* the
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* carries a second copy that
|
|
309
|
+
* context key when an agent was named, leaves it empty for the built-in
|
|
310
|
+
* assistant, owns the session under the CALLER's org (never the agent's —
|
|
311
|
+
* cross-org agents stay usable), updates the execution with the created
|
|
312
|
+
* id, and CLEARS session_spec (the Session resource is the single source
|
|
313
|
+
* of truth; the persisted execution never carries a second copy that
|
|
314
|
+
* could drift).
|
|
415
315
|
*/
|
|
416
316
|
export function newCreateSessionIfNeededStep(deps: {
|
|
417
317
|
logger: Logger;
|
|
@@ -440,12 +340,12 @@ export function newCreateSessionIfNeededStep(deps: {
|
|
|
440
340
|
hasSessionSpec: callerSpec !== undefined,
|
|
441
341
|
});
|
|
442
342
|
|
|
443
|
-
// 1. Resolve the instance when the caller's spec does not name one
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
343
|
+
// 1. Resolve the instance when the caller's spec does not name one
|
|
344
|
+
// and an agent was named: the previous step put the agent's default
|
|
345
|
+
// instance under the context key exactly then. With no agent the
|
|
346
|
+
// session is created with no instance — the built-in assistant.
|
|
447
347
|
let defaultInstanceId = "";
|
|
448
|
-
if ((callerSpec?.agentInstanceId ?? "") === "") {
|
|
348
|
+
if ((callerSpec?.agentInstanceId ?? "") === "" && agentId !== "") {
|
|
449
349
|
const resolved = ctx.get(DEFAULT_INSTANCE_ID_KEY);
|
|
450
350
|
if (typeof resolved !== "string" || resolved === "") {
|
|
451
351
|
deps.logger.error("DEFAULT_INSTANCE_ID not found in context", {
|
|
@@ -16,9 +16,13 @@
|
|
|
16
16
|
* questions have one answer; asking the blueprint's is what lets the
|
|
17
17
|
* gate precede the side effect).
|
|
18
18
|
*
|
|
19
|
-
* None of the three set is
|
|
20
|
-
* (
|
|
21
|
-
*
|
|
19
|
+
* None of the three set is the built-in assistant
|
|
20
|
+
* (agentexecution/v1/spec.proto): a new conversation with no agent, for
|
|
21
|
+
* which there is no blueprint to spend and so no target to check. The
|
|
22
|
+
* Authorize step ahead of the gate admitted it with the organization's
|
|
23
|
+
* can_create_execution_in; CreateSessionIfNeeded then creates the session
|
|
24
|
+
* with no instance, and every later turn arrives in shape 1. Pure over the
|
|
25
|
+
* record being built.
|
|
22
26
|
*/
|
|
23
27
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
24
28
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the instance create lane's authorization at the wire, on a real boot
|
|
3
|
+
* under the require-authentication posture with no unit Authorizer (the
|
|
4
|
+
* self-host's shape, the built-in Authorizer composed):
|
|
5
|
+
*
|
|
6
|
+
* - a member creates a personal instance of an org-visible agent in their
|
|
7
|
+
* organization (can_create_agent_instance is member-level; the parent's
|
|
8
|
+
* can_create_instance is its viewer set);
|
|
9
|
+
* - a member is refused an instance of a PRIVATE agent with the parent's
|
|
10
|
+
* copy — the organization's bar passed, the agent's did not;
|
|
11
|
+
* - an outsider naming an organization they hold no row in is refused
|
|
12
|
+
* with the organization's copy, before anything about the agent;
|
|
13
|
+
* - a member's wire request carrying the reserved default-instance label
|
|
14
|
+
* is refused: the label buys nothing from the wire (the reserved-label
|
|
15
|
+
* guard refuses it after the caller passes the personal bar);
|
|
16
|
+
* - the SAME labelled request the server composes in-process for the
|
|
17
|
+
* member (the default-instance self-heal's exact shape: the in-process
|
|
18
|
+
* transport, the propagated caller, the parent's organization) is
|
|
19
|
+
* admitted with no caller check and attributed to the member — the
|
|
20
|
+
* arm that keeps every lane whose run was already admitted (a guest, a
|
|
21
|
+
* channel turn, a schedule fire) working when the agent has no default
|
|
22
|
+
* instance yet.
|
|
23
|
+
*
|
|
24
|
+
* Every negative arm sits beside a positive one so the file cannot pass
|
|
25
|
+
* vacuously. The resolver's own table is create-authorization.test.ts.
|
|
26
|
+
*/
|
|
27
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
|
|
31
|
+
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
|
32
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
33
|
+
|
|
34
|
+
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
35
|
+
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
36
|
+
import { AgentInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/command_pb";
|
|
37
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
38
|
+
import { IdentityAccountCommandController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/command_pb";
|
|
39
|
+
import { OrganizationCommandController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/command_pb";
|
|
40
|
+
|
|
41
|
+
import { loadConfig } from "../../../boot/config.js";
|
|
42
|
+
import { composeServer } from "../../../boot/compose.js";
|
|
43
|
+
import type { ComposedServer } from "../../../boot/compose.js";
|
|
44
|
+
import type { ServerExtension } from "../../../extensions/registry.js";
|
|
45
|
+
import {
|
|
46
|
+
baseConfig,
|
|
47
|
+
fakeJwt,
|
|
48
|
+
fakeVerifier,
|
|
49
|
+
silentLogger,
|
|
50
|
+
transportFor,
|
|
51
|
+
} from "../../../extensions/__tests__/composed-support.js";
|
|
52
|
+
import {
|
|
53
|
+
DEFAULT_INSTANCE_LABEL,
|
|
54
|
+
RESERVED_LABEL_TRUE,
|
|
55
|
+
} from "../../../pipeline/apiresource-labels.js";
|
|
56
|
+
import {
|
|
57
|
+
IN_PROCESS_CALLER_HEADER,
|
|
58
|
+
encodeInProcessCaller,
|
|
59
|
+
} from "../../../pipeline/interceptors/auth.js";
|
|
60
|
+
import { buildDefaultInstanceRequest } from "../defaultinstance.js";
|
|
61
|
+
import {
|
|
62
|
+
INSTANCE_ORGANIZATION_DENIED_MESSAGE,
|
|
63
|
+
INSTANCE_PARENT_DENIED_MESSAGE,
|
|
64
|
+
} from "../steps.js";
|
|
65
|
+
|
|
66
|
+
const FOUNDER = "fake|founder";
|
|
67
|
+
const MEMBER = "fake|member";
|
|
68
|
+
const STRANGER = "fake|stranger";
|
|
69
|
+
const ORG = "instance-authz-org";
|
|
70
|
+
|
|
71
|
+
function organizationInput(slug: string) {
|
|
72
|
+
return {
|
|
73
|
+
apiVersion: "tenancy.stigmer.ai/v1",
|
|
74
|
+
kind: "Organization",
|
|
75
|
+
metadata: { name: slug, slug, org: "" },
|
|
76
|
+
spec: { description: slug },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function agentInput(name: string, visibility: ApiResourceVisibility) {
|
|
81
|
+
return {
|
|
82
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
83
|
+
kind: "Agent",
|
|
84
|
+
metadata: { name, org: ORG, visibility },
|
|
85
|
+
spec: { instructions: "a conformant instruction body" },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function instanceInput(
|
|
90
|
+
name: string,
|
|
91
|
+
agentId: string,
|
|
92
|
+
org: string = ORG,
|
|
93
|
+
labels: Record<string, string> = {},
|
|
94
|
+
) {
|
|
95
|
+
return {
|
|
96
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
97
|
+
kind: "AgentInstance",
|
|
98
|
+
metadata: { name, org, labels },
|
|
99
|
+
spec: { agentId, description: name },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function failureOf(promise: Promise<unknown>): Promise<ConnectError> {
|
|
104
|
+
try {
|
|
105
|
+
await promise;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return ConnectError.from(error);
|
|
108
|
+
}
|
|
109
|
+
throw new Error("expected the call to fail");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
describe("agent instance create under the built-in authorizer", () => {
|
|
113
|
+
let dir: string;
|
|
114
|
+
let server: ComposedServer;
|
|
115
|
+
let port: number;
|
|
116
|
+
let orgAgent: Agent;
|
|
117
|
+
let privateAgent: Agent;
|
|
118
|
+
|
|
119
|
+
const asFounder = () =>
|
|
120
|
+
transportFor(port, fakeJwt(FOUNDER, "founder@example.com"));
|
|
121
|
+
const asMember = () =>
|
|
122
|
+
transportFor(port, fakeJwt(MEMBER, "member@example.com"));
|
|
123
|
+
const asStranger = () =>
|
|
124
|
+
transportFor(port, fakeJwt(STRANGER, "stranger@example.com"));
|
|
125
|
+
|
|
126
|
+
beforeAll(async () => {
|
|
127
|
+
dir = mkdtempSync(path.join(tmpdir(), "instance-create-authz-"));
|
|
128
|
+
const unit: ServerExtension = {
|
|
129
|
+
name: "fake-oidc-only",
|
|
130
|
+
requireAuthentication: true,
|
|
131
|
+
identityVerifiers: [fakeVerifier],
|
|
132
|
+
};
|
|
133
|
+
server = await composeServer({
|
|
134
|
+
config: loadConfig(baseConfig(dir)),
|
|
135
|
+
logger: silentLogger,
|
|
136
|
+
extensions: [unit],
|
|
137
|
+
portOverride: 0,
|
|
138
|
+
host: "127.0.0.1",
|
|
139
|
+
});
|
|
140
|
+
port = await server.start();
|
|
141
|
+
|
|
142
|
+
// The membership rules of a self-host: a person who provisions while
|
|
143
|
+
// the organization exists joins it as a member; a person provisioned
|
|
144
|
+
// BEFORE it was founded holds no row on it. So the stranger provisions
|
|
145
|
+
// first, the founder founds, and the member provisions after.
|
|
146
|
+
await createClient(
|
|
147
|
+
IdentityAccountCommandController,
|
|
148
|
+
asFounder(),
|
|
149
|
+
).provisionMyAccount({});
|
|
150
|
+
await createClient(
|
|
151
|
+
IdentityAccountCommandController,
|
|
152
|
+
asStranger(),
|
|
153
|
+
).provisionMyAccount({});
|
|
154
|
+
await createClient(OrganizationCommandController, asFounder()).create(
|
|
155
|
+
organizationInput(ORG),
|
|
156
|
+
);
|
|
157
|
+
await createClient(
|
|
158
|
+
IdentityAccountCommandController,
|
|
159
|
+
asMember(),
|
|
160
|
+
).provisionMyAccount({});
|
|
161
|
+
|
|
162
|
+
const founderAgents = createClient(AgentCommandController, asFounder());
|
|
163
|
+
orgAgent = await founderAgents.create(
|
|
164
|
+
agentInput("Org Agent", ApiResourceVisibility.visibility_org),
|
|
165
|
+
);
|
|
166
|
+
privateAgent = await founderAgents.create(
|
|
167
|
+
agentInput("Private Agent", ApiResourceVisibility.visibility_private),
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
afterAll(async () => {
|
|
172
|
+
await server.shutdown();
|
|
173
|
+
rmSync(dir, { recursive: true, force: true });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("a member creates a personal instance of an org-visible agent in their organization", async () => {
|
|
177
|
+
const created = await createClient(
|
|
178
|
+
AgentInstanceCommandController,
|
|
179
|
+
asMember(),
|
|
180
|
+
).create(instanceInput("member-personal", orgAgent.metadata!.id));
|
|
181
|
+
expect(created.metadata?.org).toBe(ORG);
|
|
182
|
+
expect(created.status?.audit?.specAudit?.createdBy?.id).toBe(MEMBER);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("a member is refused an instance of a PRIVATE agent with the parent's copy", async () => {
|
|
186
|
+
const error = await failureOf(
|
|
187
|
+
createClient(AgentInstanceCommandController, asMember()).create(
|
|
188
|
+
instanceInput("member-on-private", privateAgent.metadata!.id),
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
expect(error.code).toBe(Code.PermissionDenied);
|
|
192
|
+
expect(error.rawMessage).toBe(INSTANCE_PARENT_DENIED_MESSAGE);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("an outsider naming the organization is refused with the organization's copy, before the agent is named", async () => {
|
|
196
|
+
const error = await failureOf(
|
|
197
|
+
createClient(AgentInstanceCommandController, asStranger()).create(
|
|
198
|
+
instanceInput("stranger-instance", orgAgent.metadata!.id),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
expect(error.code).toBe(Code.PermissionDenied);
|
|
202
|
+
expect(error.rawMessage).toBe(INSTANCE_ORGANIZATION_DENIED_MESSAGE);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("the reserved default-instance label buys nothing from the wire", async () => {
|
|
206
|
+
const error = await failureOf(
|
|
207
|
+
createClient(AgentInstanceCommandController, asMember()).create(
|
|
208
|
+
instanceInput("wire-default", orgAgent.metadata!.id, ORG, {
|
|
209
|
+
[DEFAULT_INSTANCE_LABEL]: RESERVED_LABEL_TRUE,
|
|
210
|
+
}),
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
// The personal bar admits a member on an org-visible agent; the label
|
|
214
|
+
// guard then refuses the reserved label a wire caller may not write.
|
|
215
|
+
expect(error.code).toBe(Code.InvalidArgument);
|
|
216
|
+
expect(error.rawMessage).toContain(DEFAULT_INSTANCE_LABEL);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("the same labelled request composed in-process for the member is admitted and attributed to them (the self-heal's shape)", async () => {
|
|
220
|
+
const member = await fakeVerifier.verify(
|
|
221
|
+
fakeJwt(MEMBER, "member@example.com"),
|
|
222
|
+
);
|
|
223
|
+
if (member === null) {
|
|
224
|
+
throw new Error("the fake verifier must admit the member");
|
|
225
|
+
}
|
|
226
|
+
// The agent's own default instance already exists (agent create made
|
|
227
|
+
// it); the self-heal's request is the same shape under a slug the store
|
|
228
|
+
// does not hold yet, which is what a legacy agent without one presents.
|
|
229
|
+
const request = buildDefaultInstanceRequest(orgAgent.metadata!);
|
|
230
|
+
request.metadata!.name = "org-agent-default-healed";
|
|
231
|
+
const created = await createClient(
|
|
232
|
+
AgentInstanceCommandController,
|
|
233
|
+
server.inProcessTransport,
|
|
234
|
+
).create(request, {
|
|
235
|
+
headers: { [IN_PROCESS_CALLER_HEADER]: encodeInProcessCaller(member) },
|
|
236
|
+
});
|
|
237
|
+
expect(created.metadata?.labels?.[DEFAULT_INSTANCE_LABEL]).toBe(
|
|
238
|
+
RESERVED_LABEL_TRUE,
|
|
239
|
+
);
|
|
240
|
+
expect(created.metadata?.org).toBe(ORG);
|
|
241
|
+
expect(created.status?.audit?.specAudit?.createdBy?.id).toBe(MEMBER);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the create lane's authorization questions (steps.ts,
|
|
3
|
+
* resolveInstanceCreateTargets): a personal instance asks the
|
|
4
|
+
* organization's bar (can_create_agent_instance on metadata.org) and then
|
|
5
|
+
* the parent's (can_create_instance on the agent), each with its own copy;
|
|
6
|
+
* a default instance the server composed in-process for the parent's
|
|
7
|
+
* organization asks nothing; a labelled request that arrived on the wire,
|
|
8
|
+
* or that names another organization, takes the personal bar in full; a
|
|
9
|
+
* missing parent is a thrown chain invariant, never an open lane.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { create } from "@bufbuild/protobuf";
|
|
13
|
+
|
|
14
|
+
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
15
|
+
import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
16
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
17
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
18
|
+
|
|
19
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_INSTANCE_LABEL,
|
|
22
|
+
RESERVED_LABEL_TRUE,
|
|
23
|
+
} from "../../../pipeline/apiresource-labels.js";
|
|
24
|
+
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
25
|
+
import { RequestContext } from "../../../pipeline/request-context.js";
|
|
26
|
+
import {
|
|
27
|
+
INSTANCE_ORGANIZATION_DENIED_MESSAGE,
|
|
28
|
+
INSTANCE_PARENT_DENIED_MESSAGE,
|
|
29
|
+
PARENT_AGENT_KEY,
|
|
30
|
+
resolveInstanceCreateTargets,
|
|
31
|
+
} from "../steps.js";
|
|
32
|
+
|
|
33
|
+
const parent = create(AgentSchema, {
|
|
34
|
+
metadata: { id: "agt_01parent", name: "helper", org: "acme" },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function ctxFor(
|
|
38
|
+
org: string,
|
|
39
|
+
caller: CallerIdentity,
|
|
40
|
+
labels: Record<string, string> = {},
|
|
41
|
+
stashParent = true,
|
|
42
|
+
) {
|
|
43
|
+
const ctx = new RequestContext(
|
|
44
|
+
AgentInstanceSchema,
|
|
45
|
+
create(AgentInstanceSchema, {
|
|
46
|
+
metadata: { name: "mine", org, labels },
|
|
47
|
+
spec: { agentId: parent.metadata!.id },
|
|
48
|
+
}),
|
|
49
|
+
caller,
|
|
50
|
+
ApiResourceKind.agent_instance,
|
|
51
|
+
);
|
|
52
|
+
if (stashParent) {
|
|
53
|
+
ctx.set(PARENT_AGENT_KEY, parent);
|
|
54
|
+
}
|
|
55
|
+
return ctx;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DEFAULT_LABELS = { [DEFAULT_INSTANCE_LABEL]: RESERVED_LABEL_TRUE };
|
|
59
|
+
|
|
60
|
+
describe("resolveInstanceCreateTargets", () => {
|
|
61
|
+
it("a personal instance asks the organization's bar first, then the parent's, each with its own copy", () => {
|
|
62
|
+
expect(
|
|
63
|
+
resolveInstanceCreateTargets(ctxFor("acme", testCallerIdentity())),
|
|
64
|
+
).toEqual([
|
|
65
|
+
{
|
|
66
|
+
permission: IamPermission.can_create_agent_instance,
|
|
67
|
+
resourceKind: ApiResourceKind.organization,
|
|
68
|
+
resourceId: "acme",
|
|
69
|
+
deniedMessage: INSTANCE_ORGANIZATION_DENIED_MESSAGE,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
permission: IamPermission.can_create_instance,
|
|
73
|
+
resourceKind: ApiResourceKind.agent,
|
|
74
|
+
resourceId: "agt_01parent",
|
|
75
|
+
deniedMessage: INSTANCE_PARENT_DENIED_MESSAGE,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("a cross-org personal instance asks about ITS organization, not the parent's (the marketplace case)", () => {
|
|
81
|
+
const [orgBar] = resolveInstanceCreateTargets(
|
|
82
|
+
ctxFor("consumer-org", testCallerIdentity()),
|
|
83
|
+
);
|
|
84
|
+
expect(orgBar?.resourceId).toBe("consumer-org");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("a default instance the server composed in-process for the parent's organization asks nothing", () => {
|
|
88
|
+
expect(
|
|
89
|
+
resolveInstanceCreateTargets(
|
|
90
|
+
ctxFor(
|
|
91
|
+
"acme",
|
|
92
|
+
testCallerIdentity({ callerClass: "user", origin: "in-process" }),
|
|
93
|
+
DEFAULT_LABELS,
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
).toEqual([]);
|
|
97
|
+
expect(
|
|
98
|
+
resolveInstanceCreateTargets(
|
|
99
|
+
ctxFor(
|
|
100
|
+
"acme",
|
|
101
|
+
testCallerIdentity({ callerClass: "channel", origin: "in-process" }),
|
|
102
|
+
DEFAULT_LABELS,
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("the label alone buys nothing: a labelled request from the wire takes the personal bar in full, the machine class included", () => {
|
|
109
|
+
expect(
|
|
110
|
+
resolveInstanceCreateTargets(
|
|
111
|
+
ctxFor("acme", testCallerIdentity(), DEFAULT_LABELS),
|
|
112
|
+
),
|
|
113
|
+
).toHaveLength(2);
|
|
114
|
+
expect(
|
|
115
|
+
resolveInstanceCreateTargets(
|
|
116
|
+
ctxFor(
|
|
117
|
+
"acme",
|
|
118
|
+
testCallerIdentity({ callerClass: "machine" }),
|
|
119
|
+
DEFAULT_LABELS,
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
).toHaveLength(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("a labelled in-process request for another organization is not a default instance to this step", () => {
|
|
126
|
+
expect(
|
|
127
|
+
resolveInstanceCreateTargets(
|
|
128
|
+
ctxFor(
|
|
129
|
+
"other-org",
|
|
130
|
+
testCallerIdentity({ callerClass: "user", origin: "in-process" }),
|
|
131
|
+
DEFAULT_LABELS,
|
|
132
|
+
),
|
|
133
|
+
),
|
|
134
|
+
).toHaveLength(2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("a missing parent is a broken chain invariant and throws", () => {
|
|
138
|
+
expect(() =>
|
|
139
|
+
resolveInstanceCreateTargets(
|
|
140
|
+
ctxFor("acme", testCallerIdentity(), {}, false),
|
|
141
|
+
),
|
|
142
|
+
).toThrow("parent agent not found in context");
|
|
143
|
+
});
|
|
144
|
+
});
|