@stigmer/server 3.19.1-dev.20260920100552 → 3.21.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.
Files changed (91) hide show
  1. package/dist/boot/compose.d.ts.map +1 -1
  2. package/dist/boot/compose.js +0 -1
  3. package/dist/boot/compose.js.map +1 -1
  4. package/dist/boot/inprocess.d.ts +0 -2
  5. package/dist/boot/inprocess.d.ts.map +1 -1
  6. package/dist/boot/inprocess.js +0 -6
  7. package/dist/boot/inprocess.js.map +1 -1
  8. package/dist/domain/agent/controller.d.ts +2 -2
  9. package/dist/domain/agent/controller.d.ts.map +1 -1
  10. package/dist/domain/agent/controller.js +1 -17
  11. package/dist/domain/agent/controller.js.map +1 -1
  12. package/dist/domain/agent/steps.d.ts +0 -1
  13. package/dist/domain/agent/steps.d.ts.map +1 -1
  14. package/dist/domain/agent/steps.js +1 -40
  15. package/dist/domain/agent/steps.js.map +1 -1
  16. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  17. package/dist/domain/agentexecution/controller.js +7 -9
  18. package/dist/domain/agentexecution/controller.js.map +1 -1
  19. package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
  20. package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
  21. package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
  22. package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
  23. package/dist/domain/agentexecution/create-steps.d.ts +16 -31
  24. package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
  25. package/dist/domain/agentexecution/create-steps.js +36 -102
  26. package/dist/domain/agentexecution/create-steps.js.map +1 -1
  27. package/dist/domain/agentexecution/run-target.d.ts +7 -3
  28. package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
  29. package/dist/domain/agentexecution/run-target.js.map +1 -1
  30. package/dist/domain/environment/personal.d.ts +42 -0
  31. package/dist/domain/environment/personal.d.ts.map +1 -0
  32. package/dist/domain/environment/personal.js +96 -0
  33. package/dist/domain/environment/personal.js.map +1 -0
  34. package/dist/domain/mcpserver/connect.d.ts +0 -2
  35. package/dist/domain/mcpserver/connect.d.ts.map +1 -1
  36. package/dist/domain/mcpserver/connect.js +16 -72
  37. package/dist/domain/mcpserver/connect.js.map +1 -1
  38. package/dist/domain/plugin/overlay/sanitize.js +2 -2
  39. package/dist/domain/session/controller.d.ts +0 -7
  40. package/dist/domain/session/controller.d.ts.map +1 -1
  41. package/dist/domain/session/controller.js +11 -16
  42. package/dist/domain/session/controller.js.map +1 -1
  43. package/dist/domain/session/run-target.d.ts +9 -8
  44. package/dist/domain/session/run-target.d.ts.map +1 -1
  45. package/dist/domain/session/run-target.js.map +1 -1
  46. package/dist/domain/session/steps.d.ts +0 -27
  47. package/dist/domain/session/steps.d.ts.map +1 -1
  48. package/dist/domain/session/steps.js +10 -151
  49. package/dist/domain/session/steps.js.map +1 -1
  50. package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
  51. package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
  52. package/dist/domain/workflowexecution/create-steps.js +4 -3
  53. package/dist/domain/workflowexecution/create-steps.js.map +1 -1
  54. package/dist/pipeline/apiresource-labels.d.ts +2 -2
  55. package/dist/pipeline/apiresource-labels.js +2 -2
  56. package/dist/pipeline/errors.d.ts +2 -2
  57. package/dist/pipeline/errors.js +2 -2
  58. package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
  59. package/dist/pipeline/steps/guard-reserved-labels.js +6 -5
  60. package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
  61. package/package.json +6 -6
  62. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -1
  63. package/src/boot/compose.ts +0 -1
  64. package/src/boot/inprocess.ts +0 -9
  65. package/src/domain/agent/__tests__/agent.test.ts +0 -68
  66. package/src/domain/agent/controller.ts +3 -38
  67. package/src/domain/agent/steps.ts +1 -60
  68. package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
  69. package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
  70. package/src/domain/agentexecution/controller.ts +6 -10
  71. package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
  72. package/src/domain/agentexecution/create-steps.ts +38 -138
  73. package/src/domain/agentexecution/run-target.ts +7 -3
  74. package/src/domain/environment/__tests__/personal.test.ts +143 -0
  75. package/src/domain/environment/personal.ts +141 -0
  76. package/src/domain/mcpserver/connect.ts +21 -82
  77. package/src/domain/plugin/overlay/sanitize.ts +2 -2
  78. package/src/domain/session/__tests__/run-gate.test.ts +19 -52
  79. package/src/domain/session/__tests__/session.test.ts +27 -203
  80. package/src/domain/session/controller.ts +10 -30
  81. package/src/domain/session/run-target.ts +9 -8
  82. package/src/domain/session/steps.ts +9 -235
  83. package/src/domain/workflowexecution/create-steps.ts +4 -3
  84. package/src/pipeline/apiresource-labels.ts +2 -2
  85. package/src/pipeline/errors.ts +2 -2
  86. package/src/pipeline/steps/guard-reserved-labels.ts +6 -5
  87. package/dist/domain/agent/defaultagent.d.ts +0 -37
  88. package/dist/domain/agent/defaultagent.d.ts.map +0 -1
  89. package/dist/domain/agent/defaultagent.js +0 -104
  90. package/dist/domain/agent/defaultagent.js.map +0 -1
  91. package/src/domain/agent/defaultagent.ts +0 -135
@@ -42,6 +42,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
42
42
  import { TokenEndpointAuthMethod } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
43
43
 
44
44
  import type { Logger } from "../../boot/logger.js";
45
+ import { resolveDeclaredFromPersonalEnvironment } from "../environment/personal.js";
45
46
  import type { Authorizer } from "../../extensions/authorizer.js";
46
47
  import type { CallerIdentity } from "../../extensions/identity.js";
47
48
  import { authorizeDirect } from "../../pipeline/steps/authorize.js";
@@ -150,9 +151,6 @@ export const ASYNC_CONNECT_TIMEOUT: ConnectBudget = {
150
151
  */
151
152
  export const BEST_EFFORT_CONNECT_GET_BUFFER_MS = 15_000;
152
153
 
153
- /** The label selecting the user's personal environment (connect.go:74). */
154
- export const PERSONAL_ENV_LABEL = "stigmer.ai/personal";
155
-
156
154
  /**
157
155
  * The narrow environment read surface the connect lanes consume for
158
156
  * personal-environment resolution — satisfied by the composition root's
@@ -687,34 +685,29 @@ async function resolveOAuthVarsFromManagedEnv(
687
685
  }
688
686
 
689
687
  /**
690
- * Reads environment variables from the user's personal environment
691
- * (labeled stigmer.ai/personal=true; Go resolveFromPersonalEnvironment).
692
- * Required variables (optional=false, the default) must be present;
693
- * optional variables are included when available but silently skipped
694
- * when missing.
688
+ * Reads a server's declared variables from the user's personal environment
689
+ * (Go resolveFromPersonalEnvironment) through the shared lookup in
690
+ * domain/environment/personal.ts, and gives its outcomes the connect
691
+ * lane's meaning: a person asked to connect a server and cannot without
692
+ * the credential, so a missing personal environment and a missing required
693
+ * key both refuse with FailedPrecondition. Optional variables are included
694
+ * when available and silently skipped when missing.
695
695
  */
696
696
  async function resolveFromPersonalEnvironment(
697
697
  deps: McpServerConnectDeps,
698
698
  org: string,
699
699
  envDecls: { [key: string]: EnvVarDeclaration },
700
700
  ): Promise<{ [key: string]: ExecutionValue }> {
701
- const requiredKeys: string[] = [];
702
- for (const [k, decl] of Object.entries(envDecls)) {
703
- if (!decl.optional) {
704
- requiredKeys.push(k);
705
- }
706
- }
707
-
708
- let listResponse: EnvironmentList;
709
- try {
710
- listResponse = await deps.environmentReader.list({
711
- org,
712
- labels: { [PERSONAL_ENV_LABEL]: "true" },
713
- });
714
- } catch (error) {
715
- throw internalError(error, "failed to list personal environments");
716
- }
717
- if (listResponse.totalCount === 0 || listResponse.items.length === 0) {
701
+ const resolution = await resolveDeclaredFromPersonalEnvironment(
702
+ deps.environmentReader,
703
+ deps.logger,
704
+ org,
705
+ envDecls,
706
+ );
707
+ if (resolution.kind === "no-personal-environment") {
708
+ const requiredKeys = Object.entries(envDecls)
709
+ .filter(([, decl]) => !decl.optional)
710
+ .map(([key]) => key);
718
711
  if (requiredKeys.length === 0) {
719
712
  return {};
720
713
  }
@@ -723,66 +716,12 @@ async function resolveFromPersonalEnvironment(
723
716
  `personal environment not found for org '${org}'; save required credentials first: [${requiredKeys.join(" ")}]`,
724
717
  );
725
718
  }
726
-
727
- const personalEnv = listResponse.items[0];
728
- const personalEnvId = personalEnv?.metadata?.id ?? "";
729
- const storedKeys = new Set(Object.keys(personalEnv?.spec?.data ?? {}));
730
-
731
- const result: { [key: string]: ExecutionValue } = {};
732
- const missing: string[] = [];
733
-
734
- for (const [key, decl] of Object.entries(envDecls)) {
735
- if (!storedKeys.has(key)) {
736
- if (decl.optional) {
737
- deps.logger.debug(
738
- "Optional env var not in personal environment — skipping",
739
- { key },
740
- );
741
- continue;
742
- }
743
- missing.push(key);
744
- continue;
745
- }
746
-
747
- let secretValue: EnvironmentSpecValue;
748
- try {
749
- secretValue = await deps.environmentReader.getSecretValue({
750
- environmentId: personalEnvId,
751
- key,
752
- });
753
- } catch (error) {
754
- deps.logger.warn("Failed to get secret value from personal environment", {
755
- key,
756
- personal_env_id: personalEnvId,
757
- error: error instanceof Error ? error.message : String(error),
758
- });
759
- if (decl.optional) {
760
- continue;
761
- }
762
- missing.push(key);
763
- continue;
764
- }
765
- if (secretValue.value === "") {
766
- if (decl.optional) {
767
- continue;
768
- }
769
- missing.push(key);
770
- continue;
771
- }
772
-
773
- result[key] = create(ExecutionValueSchema, {
774
- value: secretValue.value,
775
- isSecret: decl.isSecret,
776
- });
777
- }
778
-
779
- if (missing.length > 0) {
719
+ if (resolution.missing.length > 0) {
780
720
  throw failedPreconditionError(
781
- `missing required credentials in personal environment: [${missing.join(" ")}]`,
721
+ `missing required credentials in personal environment: [${resolution.missing.join(" ")}]`,
782
722
  );
783
723
  }
784
-
785
- return result;
724
+ return { ...resolution.values };
786
725
  }
787
726
 
788
727
  /**
@@ -5,8 +5,8 @@
5
5
  * lane passes GuardReservedLabels structurally (the controller stamps the
6
6
  * membership labels by design), so this module is the one place plugin-
7
7
  * provided metadata is judged before it can ride that lane. Without it an
8
- * archive could carry `stigmer.ai/default-agent: "true"` in its agent
9
- * overlay and become every organization's default agent.
8
+ * archive could carry any `stigmer.ai/*` label in its overlays and have
9
+ * the server act on a platform decision the plugin's author made.
10
10
  *
11
11
  * Four rules, each one sentence with the document's path:
12
12
  * - a `stigmer.ai/*` label costs `can_write_reserved_labels` on
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Pins the run gate's SPLICE in session-create over the real router (P1
3
- * sp.run-gate): the AuthorizeRunTarget step sits after
4
- * ResolveDefaultAgentInstance and before Persist, so (1) a denied caller
5
- * answers PERMISSION_DENIED with the domain's byte-pinned copy and leaves
6
- * no session row; (2) the check names the instance the CHAIN resolved,
7
- * not only the one the caller typed — the session-first shape (empty
8
- * spec) is checked against the platform default agent's default instance;
3
+ * sp.run-gate): the AuthorizeRunTarget step sits right after Authorize
4
+ * and before Persist, so (1) a denied caller answers PERMISSION_DENIED
5
+ * with the domain's byte-pinned copy and leaves no session row; (2) the
6
+ * built-in-assistant shape (empty spec) makes NO run-gate check and
7
+ * creates the session even under a denying run-gate authorizer — the
8
+ * organization's own create permission admitted it in the Authorize step;
9
9
  * (3) an allowed caller creates the session exactly as before. The
10
10
  * authorizer under test answers only run-gate checks (runGateOnlyAuthorizer),
11
11
  * so every refusal here is the run gate's and nobody else's.
@@ -20,11 +20,9 @@ import { Code, ConnectError, createClient } from "@connectrpc/connect";
20
20
  import { createGrpcTransport } from "@connectrpc/connect-node";
21
21
 
22
22
  import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
23
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
24
23
  import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
25
24
  import { SessionCommandController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/command_pb";
26
25
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
27
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
28
26
  import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
29
27
 
30
28
  import { loadConfig } from "../../../boot/config.js";
@@ -33,10 +31,6 @@ import type { ComposedServer } from "../../../boot/compose.js";
33
31
  import { createLogger } from "../../../boot/logger.js";
34
32
  import type { AuthzDecision } from "../../../extensions/authorizer.js";
35
33
  import { runGateOnlyAuthorizer } from "../../../pipeline/__tests__/support.js";
36
- import {
37
- DEFAULT_AGENT_LABEL,
38
- DEFAULT_AGENT_LABEL_VALUE,
39
- } from "../../agent/defaultagent.js";
40
34
  import { runAgentInstanceDeniedMessage } from "../constants.js";
41
35
 
42
36
  const silentLogger = createLogger({
@@ -99,23 +93,6 @@ async function createAgent(name: string): Promise<string> {
99
93
  return agent.metadata!.id;
100
94
  }
101
95
 
102
- /** The default-agent label is operator-seeded state; stamp it on the row. */
103
- async function markDefaultAgent(agentId: string): Promise<void> {
104
- const stored = await server.store.getResource(
105
- ApiResourceKind.agent,
106
- agentId,
107
- AgentSchema,
108
- );
109
- stored.metadata!.labels[DEFAULT_AGENT_LABEL] = DEFAULT_AGENT_LABEL_VALUE;
110
- stored.metadata!.visibility = ApiResourceVisibility.visibility_public;
111
- await server.store.saveResource(
112
- ApiResourceKind.agent,
113
- agentId,
114
- AgentSchema,
115
- stored,
116
- );
117
- }
118
-
119
96
  async function captureError(
120
97
  run: () => Promise<unknown>,
121
98
  ): Promise<ConnectError> {
@@ -154,33 +131,23 @@ describe("session-create run gate (composed server)", () => {
154
131
  expect(await sessionCount(), "no row behind a denial").toBe(before);
155
132
  });
156
133
 
157
- it("checks the instance the chain RESOLVED for the session-first shape", async () => {
134
+ it("makes no check for the built-in assistant (empty spec) and creates the session", async () => {
158
135
  decision = { kind: "deny", reason: "" };
159
136
  gate.runGateChecks.length = 0;
160
- const agentId = await createAgent("Default agent for the run gate");
161
- await markDefaultAgent(agentId);
137
+ const before = await sessionCount();
162
138
 
163
- const err = await captureError(() =>
164
- sessionCommand.create({
165
- apiVersion: API_VERSION,
166
- kind: "Session",
167
- metadata: { name: "Session-first", org: ORG },
168
- spec: {},
169
- }),
170
- );
139
+ const session = await sessionCommand.create({
140
+ apiVersion: API_VERSION,
141
+ kind: "Session",
142
+ metadata: { name: "Session-first", org: ORG },
143
+ spec: {},
144
+ });
171
145
 
172
- expect(err.code).toBe(Code.PermissionDenied);
173
- // ResolveDefaultAgentInstance created (or found) the default agent's
174
- // default instance BEFORE the gate — the inherited pre-gate side
175
- // effect the slot table records — and the gate names exactly it.
176
- const agent = await agentQuery.get({ value: agentId });
177
- const resolvedInstanceId = agent.status?.defaultInstanceId ?? "";
178
- expect(resolvedInstanceId).not.toBe("");
179
- expect(gate.runGateChecks).toHaveLength(1);
180
- expect(gate.runGateChecks[0].resourceId).toBe(resolvedInstanceId);
181
- expect(err.rawMessage).toBe(
182
- runAgentInstanceDeniedMessage(resolvedInstanceId),
183
- );
146
+ // No instance, no blueprint to spend, no target: the gate is silent
147
+ // and the denying authorizer is never asked.
148
+ expect(gate.runGateChecks).toHaveLength(0);
149
+ expect(session.spec?.agentInstanceId).toBe("");
150
+ expect(await sessionCount()).toBe(before + 1);
184
151
  });
185
152
 
186
153
  it("not-found answers NOT_FOUND naming the instance (the stigmer#224 order)", async () => {
@@ -1,16 +1,13 @@
1
1
  /**
2
2
  * Pins the session domain against Go's pkg/domain/session tests — through
3
3
  * the REAL stack: a composed server on an ephemeral port, a native gRPC
4
- * client, the full interceptor chain, and the DD-002 in-process
5
- * agentinstance CREATE edge (session create's default-instance self-heal
6
- * runs through the router transport).
4
+ * client, and the full interceptor chain.
7
5
  *
8
6
  * The load-bearing pins:
9
- * - ResolveDefaultAgentInstance fills the session's agent_instance_id
10
- * from the platform default agent (newState, not input — the persisted
11
- * session carries it); the on-the-fly default-instance creation path
12
- * writes the agent's status.default_instance_id; the not-configured /
13
- * not-public arms carry Go WrapError's exact double-wrapped copy;
7
+ * - an empty agent_instance_id is the built-in assistant: create stores
8
+ * it empty (nothing resolves it into an instance), an explicit id is
9
+ * kept as given, and a session may gain an agent or drop back to the
10
+ * assistant on update — the instance is not an immutable field;
14
11
  * - harness immutability locks only once harness_state_id is non-empty,
15
12
  * with UNSPECIFIED==NATIVE equivalence in both directions and the
16
13
  * exact FAILED_PRECONDITION copy;
@@ -39,11 +36,8 @@ import { createGrpcTransport } from "@connectrpc/connect-node";
39
36
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
40
37
 
41
38
  import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
42
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
43
39
  import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
44
40
  import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
45
- import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
46
- import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/query_pb";
47
41
  import { SessionCommandController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/command_pb";
48
42
  import { SessionQueryController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/query_pb";
49
43
  import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
@@ -53,7 +47,6 @@ import {
53
47
  ExecutionTarget,
54
48
  Harness,
55
49
  } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
56
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
57
50
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
58
51
 
59
52
  import { loadConfig } from "../../../boot/config.js";
@@ -70,10 +63,6 @@ import {
70
63
  ROUTING_GLOBAL,
71
64
  newConfigFromEnv,
72
65
  } from "../../agentexecution/temporal/config.js";
73
- import {
74
- DEFAULT_AGENT_LABEL,
75
- DEFAULT_AGENT_LABEL_VALUE,
76
- } from "../../agent/defaultagent.js";
77
66
  import { newValidateExecutionTargetImmutabilityStep } from "../steps.js";
78
67
 
79
68
  const silentLogger = createLogger({
@@ -92,7 +81,6 @@ let dir: string;
92
81
  let server: ComposedServer;
93
82
  let transport: Transport;
94
83
  let agentCommand: Client<typeof AgentCommandController>;
95
- let instanceQuery: Client<typeof AgentInstanceQueryController>;
96
84
  let command: Client<typeof SessionCommandController>;
97
85
  let query: Client<typeof SessionQueryController>;
98
86
 
@@ -120,7 +108,6 @@ beforeAll(async () => {
120
108
  const port = await server.start();
121
109
  transport = createGrpcTransport({ baseUrl: `http://127.0.0.1:${port}` });
122
110
  agentCommand = createClient(AgentCommandController, transport);
123
- instanceQuery = createClient(AgentInstanceQueryController, transport);
124
111
  command = createClient(SessionCommandController, transport);
125
112
  query = createClient(SessionQueryController, transport);
126
113
  });
@@ -141,33 +128,6 @@ async function createAgent(name: string) {
141
128
  });
142
129
  }
143
130
 
144
- /**
145
- * Stamps the default-agent label (and optionally visibility_public)
146
- * directly on the stored agent row — the reserved stigmer.ai/* label is
147
- * operator-seeded state, not something the OSS write path guards, and the
148
- * direct write keeps the test independent of create-time visibility rules.
149
- */
150
- async function markDefaultAgent(
151
- agentId: string,
152
- opts: { public: boolean },
153
- ): Promise<void> {
154
- const stored = await server.store.getResource(
155
- ApiResourceKind.agent,
156
- agentId,
157
- AgentSchema,
158
- );
159
- stored.metadata!.labels[DEFAULT_AGENT_LABEL] = DEFAULT_AGENT_LABEL_VALUE;
160
- if (opts.public) {
161
- stored.metadata!.visibility = ApiResourceVisibility.visibility_public;
162
- }
163
- await server.store.saveResource(
164
- ApiResourceKind.agent,
165
- agentId,
166
- AgentSchema,
167
- stored,
168
- );
169
- }
170
-
171
131
  let sessionCounter = 0;
172
132
  async function createSession(
173
133
  spec: Partial<Omit<SessionSpec, "$typeName">> = {},
@@ -252,108 +212,36 @@ async function grpcError(run: () => Promise<unknown>): Promise<ConnectError> {
252
212
  }
253
213
  }
254
214
 
255
- // The resolve tests run FIRST: the not-configured arm requires that no
256
- // agent carries the default-agent label yet, and later tests only create
257
- // unlabeled agents (or clean their labeled ones up).
258
- describe("session create — ResolveDefaultAgentInstance", () => {
259
- it("rejects a create with no default agent configured (exact NotFound copy, Go's double wrap)", async () => {
260
- const error = await grpcError(() => createSession({}));
261
- expect(error.code).toBe(Code.NotFound);
262
- expect(error.rawMessage).toBe(
263
- "No default agent available. Ensure an agent with label " +
264
- "stigmer.ai/default-agent=true and visibility_public exists: " +
265
- "no default agent available on this platform: " +
266
- "no agent labeled stigmer.ai/default-agent=true",
267
- );
215
+ describe("session create and update — the built-in assistant", () => {
216
+ it("stores an empty agent_instance_id as given: no agent is resolved for the session", async () => {
217
+ const session = await createSession({});
218
+ expect(session.spec?.agentInstanceId).toBe("");
219
+ const fetched = await query.get({ value: session.metadata!.id });
220
+ expect(fetched.spec?.agentInstanceId).toBe("");
221
+ await command.delete({ value: session.metadata!.id });
268
222
  });
269
223
 
270
- it("rejects when the labeled default agent is not visibility_public (exact FailedPrecondition copy)", async () => {
271
- const agent = await createAgent("Non Public Default Agent");
272
- await markDefaultAgent(agent.metadata!.id, { public: false });
273
- try {
274
- const error = await grpcError(() => createSession({}));
275
- expect(error.code).toBe(Code.FailedPrecondition);
276
- expect(error.rawMessage).toBe(
277
- "Default agent exists but is not visibility_public: " +
278
- "agents labeled stigmer.ai/default-agent=true exist but none is visibility_public",
279
- );
280
- } finally {
281
- await agentCommand.delete({ value: agent.metadata!.id });
282
- }
224
+ it("keeps an explicit agent_instance_id as given", async () => {
225
+ const session = await createSession({ agentInstanceId: "ain_explicit" });
226
+ expect(session.spec?.agentInstanceId).toBe("ain_explicit");
227
+ await command.delete({ value: session.metadata!.id });
283
228
  });
284
229
 
285
- it("fills the persisted session's agent_instance_id from the default agent's existing default instance", async () => {
286
- const agent = await createAgent("Configured Default Agent");
287
- const defaultInstanceId = agent.status!.defaultInstanceId;
288
- expect(defaultInstanceId).not.toBe("");
289
- await markDefaultAgent(agent.metadata!.id, { public: true });
290
- try {
291
- const session = await createSession({});
292
- // The resolve step wrote newState.spec (the clone Persist saves) —
293
- // the id survives the round trip to storage.
294
- expect(session.spec?.agentInstanceId).toBe(defaultInstanceId);
295
- const fetched = await query.get({ value: session.metadata!.id });
296
- expect(fetched.spec?.agentInstanceId).toBe(defaultInstanceId);
297
- await command.delete({ value: session.metadata!.id });
298
- } finally {
299
- await agentCommand.delete({ value: agent.metadata!.id });
300
- }
301
- });
230
+ it("lets a conversation gain an agent and drop back to the assistant on update", async () => {
231
+ const session = await createSession({});
302
232
 
303
- it("creates the default instance on the fly and writes the agent's status.default_instance_id", async () => {
304
- // Seeded directly: a labeled public agent with NO default instance —
305
- // the legacy shape the self-heal path exists for.
306
- const agentId = "agt_sessiontest_selfheal";
307
- const seeded = create(AgentSchema, {
308
- apiVersion: API_VERSION,
309
- kind: "Agent",
310
- metadata: {
311
- id: agentId,
312
- name: "Self Heal Default Agent",
313
- slug: "self-heal-default-agent",
314
- org: ORG,
315
- visibility: ApiResourceVisibility.visibility_public,
316
- labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
317
- },
318
- spec: { instructions: "seeded for the self-heal path" },
319
- });
320
- await server.store.saveResource(
321
- ApiResourceKind.agent,
322
- agentId,
323
- AgentSchema,
324
- seeded,
233
+ const withAgent = await command.update(
234
+ updateInput(session, { agentInstanceId: "ain_picked" }),
325
235
  );
326
- try {
327
- const session = await createSession({});
328
- const resolvedId = session.spec?.agentInstanceId ?? "";
329
- expect(resolvedId).not.toBe("");
330
-
331
- // The created instance is real (rode the in-process CREATE edge and
332
- // its full pipeline) and follows the defaultinstance factory shape.
333
- const instance = await instanceQuery.get({ value: resolvedId });
334
- expect(instance.spec?.agentId).toBe(agentId);
335
- expect(instance.metadata?.slug).toBe("self-heal-default-agent-default");
336
-
337
- // The agent's server-owned pointer was persisted (explicit agent
338
- // kind — the pipeline's own ctx kind is session).
339
- const storedAgent = await server.store.getResource(
340
- ApiResourceKind.agent,
341
- agentId,
342
- AgentSchema,
343
- );
344
- expect(storedAgent.status?.defaultInstanceId).toBe(resolvedId);
236
+ expect(withAgent.spec?.agentInstanceId).toBe("ain_picked");
345
237
 
346
- await command.delete({ value: session.metadata!.id });
347
- } finally {
348
- await agentCommand.delete({ value: agentId });
349
- }
350
- });
238
+ const dropped = await command.update(
239
+ updateInput(withAgent, { agentInstanceId: "" }),
240
+ );
241
+ expect(dropped.spec?.agentInstanceId).toBe("");
242
+ const fetched = await query.get({ value: session.metadata!.id });
243
+ expect(fetched.spec?.agentInstanceId).toBe("");
351
244
 
352
- it("is a no-op when agent_instance_id is provided (no default agent needed)", async () => {
353
- // No labeled agent exists at this point; an explicit id must not
354
- // trigger resolution.
355
- const session = await createSession({ agentInstanceId: "ain_explicit" });
356
- expect(session.spec?.agentInstanceId).toBe("ain_explicit");
357
245
  await command.delete({ value: session.metadata!.id });
358
246
  });
359
247
  });
@@ -716,67 +604,3 @@ describe("session delete — active-execution guard and cascade", () => {
716
604
  await command.delete({ value: survivor.metadata!.id });
717
605
  });
718
606
  });
719
-
720
- describe("session create — CreateAsSystem failure wire contract (oss#852)", () => {
721
- it("answers the inner code with Go's %w-wrapped transport-formatted message", async () => {
722
- // A labeled public default agent with NO status.default_instance_id
723
- // (the self-heal shape) whose deterministic default-instance slug is
724
- // already occupied: the in-process CreateAsSystem hits CheckDuplicate's
725
- // AlreadyExists. Go wraps that error with fmt.Errorf("%w"), so the
726
- // wire message embeds grpc-go's `rpc error: code = X desc = ...`
727
- // rendering of the inner error — the leak filed as stigmer/stigmer#852,
728
- // mirrored byte-for-byte until the both-editions post-cutover fix.
729
- const agentId = "agt_wrap_session";
730
- const seeded = create(AgentSchema, {
731
- apiVersion: API_VERSION,
732
- kind: "Agent",
733
- metadata: {
734
- id: agentId,
735
- name: "Wrap Mirror Default Agent",
736
- slug: "wrap-mirror-default-agent",
737
- org: ORG,
738
- visibility: ApiResourceVisibility.visibility_public,
739
- labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
740
- },
741
- spec: { instructions: "seeded for the wrapped-error mirror test" },
742
- });
743
- await server.store.saveResource(
744
- ApiResourceKind.agent,
745
- agentId,
746
- AgentSchema,
747
- seeded,
748
- );
749
- const occupier = create(AgentInstanceSchema, {
750
- apiVersion: API_VERSION,
751
- kind: "AgentInstance",
752
- metadata: {
753
- id: "ain_wrap_occupied",
754
- name: "wrap-mirror-default-agent-default",
755
- slug: "wrap-mirror-default-agent-default",
756
- org: ORG,
757
- },
758
- spec: { agentId },
759
- });
760
- await server.store.saveResource(
761
- ApiResourceKind.agent_instance,
762
- "ain_wrap_occupied",
763
- AgentInstanceSchema,
764
- occupier,
765
- );
766
-
767
- try {
768
- const error = await grpcError(() => createSession({}));
769
- expect(error.code).toBe(Code.AlreadyExists);
770
- expect(error.rawMessage).toBe(
771
- "failed to create default instance for default agent: rpc error: " +
772
- "code = AlreadyExists desc = AgentInstance already exists: slug " +
773
- "'wrap-mirror-default-agent-default' in org 'acme' " +
774
- "(id: ain_wrap_occupied)",
775
- );
776
- } finally {
777
- // The cascade sweeps the occupying instance (spec.agent_id match)
778
- // and removes the default-agent label with the agent.
779
- await agentCommand.delete({ value: agentId });
780
- }
781
- });
782
- });
@@ -110,11 +110,9 @@ import {
110
110
  newListAllSessionsStep,
111
111
  newRecordHarnessStateHistoryStep,
112
112
  newRejectDeleteWithActiveExecutionsStep,
113
- newResolveDefaultAgentInstanceStep,
114
113
  newValidateExecutionTargetImmutabilityStep,
115
114
  newValidateHarnessImmutabilityStep,
116
115
  } from "./steps.js";
117
- import type { AgentInstanceCreatorProvider } from "./steps.js";
118
116
 
119
117
  export interface SessionControllerDeps {
120
118
  readonly store: Store;
@@ -129,12 +127,6 @@ export interface SessionControllerDeps {
129
127
  * same deployment default dispatch uses (oss#397).
130
128
  */
131
129
  readonly temporalConfig: AgentExecutionTemporalConfig;
132
- /**
133
- * The agentinstance in-process CREATE edge — a lazy provider because the
134
- * routes↔clients definition cycle resolves at request time (the ratified
135
- * DI story, DD-002).
136
- */
137
- readonly agentInstanceCreator: AgentInstanceCreatorProvider;
138
130
  /** The composed slot registrations — this domain's create slot (O4). */
139
131
  readonly gateSteps: ResolvedGateSteps;
140
132
  /**
@@ -177,23 +169,19 @@ function kindOf(ctx: HandlerContext): ApiResourceKind {
177
169
  }
178
170
 
179
171
  /**
180
- * Create — chain per Go buildCreatePipeline. ResolveDefaultAgentInstance
181
- * runs BEFORE ValidateProto: when agent_instance_id is omitted, the
182
- * resolution fills it in before validation sees the spec.
183
- *
184
- * AuthorizeRunTarget (the run gate, P1 sp.run-gate) follows it directly:
185
- * the first step after the instance the session binds to is known, so a
186
- * caller who may not run that instance is refused before validation reads
187
- * anything further and before any step the chain owns side-effects. The
188
- * default-instance creation INSIDE the resolution step still precedes it —
189
- * the inherited pre-gate side effect recorded below, unchanged here.
172
+ * Create — chain per Go buildCreatePipeline. AuthorizeRunTarget (the run
173
+ * gate, P1 sp.run-gate) runs right after Authorize: the instance the
174
+ * session binds to is the caller's own spec, so a caller who may not run
175
+ * that instance is refused before validation reads anything further and
176
+ * before any step the chain owns side-effects. An empty agent_instance_id
177
+ * is the built-in assistant, for which the gate makes no target check:
178
+ * Authorize's can_create_session on the organization already admitted the
179
+ * conversation (sessionRunTarget's header).
190
180
  *
191
181
  * The pre-side-effect gate slot splices before Persist, after the last
192
182
  * pure step (O4 plan-gate ruling Q2, mirroring the Java baseline's
193
- * post-resolution gate position). The default-instance resolution above
194
- * it side-effects PRE-gate in BOTH editions — a gate refusal can leave a
195
- * created default instance behind; inherited Java semantics, not a new
196
- * compromise.
183
+ * post-resolution gate position). Every step above it is pure, so a gate
184
+ * refusal leaves nothing behind.
197
185
  */
198
186
  async function createSession(
199
187
  deps: SessionControllerDeps,
@@ -213,14 +201,6 @@ async function createSession(
213
201
  .addStep(
214
202
  newAuthorizeStep(SessionCommandController.method.create, deps.authorizer),
215
203
  )
216
- .addStep(
217
- newResolveDefaultAgentInstanceStep(
218
- deps.store,
219
- deps.agentInstanceCreator,
220
- deps.logger,
221
- deps.authorizationLifecycle,
222
- ),
223
- )
224
204
  .addStep(newAuthorizeRunTargetStep(deps.authorizer, sessionRunTarget))
225
205
  .addStep(newValidateProtoStep())
226
206
  .addStep(newValidateVisibilityStep())