@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.
Files changed (257) hide show
  1. package/dist/authorization/evaluator.d.ts +7 -1
  2. package/dist/authorization/evaluator.d.ts.map +1 -1
  3. package/dist/authorization/evaluator.js.map +1 -1
  4. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  5. package/dist/authorization/list-read-scope.js +19 -11
  6. package/dist/authorization/list-read-scope.js.map +1 -1
  7. package/dist/authorization/model/organization.d.ts.map +1 -1
  8. package/dist/authorization/model/organization.js +2 -0
  9. package/dist/authorization/model/organization.js.map +1 -1
  10. package/dist/boot/compose.d.ts.map +1 -1
  11. package/dist/boot/compose.js +0 -1
  12. package/dist/boot/compose.js.map +1 -1
  13. package/dist/boot/inprocess.d.ts +0 -2
  14. package/dist/boot/inprocess.d.ts.map +1 -1
  15. package/dist/boot/inprocess.js +0 -6
  16. package/dist/boot/inprocess.js.map +1 -1
  17. package/dist/domain/agent/controller.d.ts +2 -2
  18. package/dist/domain/agent/controller.d.ts.map +1 -1
  19. package/dist/domain/agent/controller.js +3 -17
  20. package/dist/domain/agent/controller.js.map +1 -1
  21. package/dist/domain/agent/steps.d.ts +0 -1
  22. package/dist/domain/agent/steps.d.ts.map +1 -1
  23. package/dist/domain/agent/steps.js +1 -40
  24. package/dist/domain/agent/steps.js.map +1 -1
  25. package/dist/domain/agentchannel/controller.d.ts.map +1 -1
  26. package/dist/domain/agentchannel/controller.js +6 -1
  27. package/dist/domain/agentchannel/controller.js.map +1 -1
  28. package/dist/domain/agentchannel/steps.d.ts +19 -0
  29. package/dist/domain/agentchannel/steps.d.ts.map +1 -1
  30. package/dist/domain/agentchannel/steps.js +32 -0
  31. package/dist/domain/agentchannel/steps.js.map +1 -1
  32. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  33. package/dist/domain/agentexecution/controller.js +7 -9
  34. package/dist/domain/agentexecution/controller.js.map +1 -1
  35. package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
  36. package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
  37. package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
  38. package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
  39. package/dist/domain/agentexecution/create-steps.d.ts +16 -31
  40. package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
  41. package/dist/domain/agentexecution/create-steps.js +36 -102
  42. package/dist/domain/agentexecution/create-steps.js.map +1 -1
  43. package/dist/domain/agentexecution/run-target.d.ts +7 -3
  44. package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
  45. package/dist/domain/agentexecution/run-target.js.map +1 -1
  46. package/dist/domain/agentinstance/controller.d.ts +5 -5
  47. package/dist/domain/agentinstance/controller.d.ts.map +1 -1
  48. package/dist/domain/agentinstance/controller.js +6 -1
  49. package/dist/domain/agentinstance/controller.js.map +1 -1
  50. package/dist/domain/agentinstance/steps.d.ts +43 -8
  51. package/dist/domain/agentinstance/steps.d.ts.map +1 -1
  52. package/dist/domain/agentinstance/steps.js +71 -8
  53. package/dist/domain/agentinstance/steps.js.map +1 -1
  54. package/dist/domain/agentshare/controller.d.ts.map +1 -1
  55. package/dist/domain/agentshare/controller.js +7 -1
  56. package/dist/domain/agentshare/controller.js.map +1 -1
  57. package/dist/domain/agentshare/steps.d.ts +58 -0
  58. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  59. package/dist/domain/agentshare/steps.js +109 -4
  60. package/dist/domain/agentshare/steps.js.map +1 -1
  61. package/dist/domain/artifact/controller.js +21 -25
  62. package/dist/domain/artifact/controller.js.map +1 -1
  63. package/dist/domain/channelapp/controller.d.ts.map +1 -1
  64. package/dist/domain/channelapp/controller.js +2 -0
  65. package/dist/domain/channelapp/controller.js.map +1 -1
  66. package/dist/domain/environment/controller.d.ts.map +1 -1
  67. package/dist/domain/environment/controller.js +2 -0
  68. package/dist/domain/environment/controller.js.map +1 -1
  69. package/dist/domain/environment/personal.d.ts +42 -0
  70. package/dist/domain/environment/personal.d.ts.map +1 -0
  71. package/dist/domain/environment/personal.js +96 -0
  72. package/dist/domain/environment/personal.js.map +1 -0
  73. package/dist/domain/executioncontext/controller.d.ts.map +1 -1
  74. package/dist/domain/executioncontext/controller.js +2 -0
  75. package/dist/domain/executioncontext/controller.js.map +1 -1
  76. package/dist/domain/executioncontext/steps.d.ts.map +1 -1
  77. package/dist/domain/executioncontext/steps.js +2 -1
  78. package/dist/domain/executioncontext/steps.js.map +1 -1
  79. package/dist/domain/mcpserver/connect.d.ts +0 -2
  80. package/dist/domain/mcpserver/connect.d.ts.map +1 -1
  81. package/dist/domain/mcpserver/connect.js +24 -79
  82. package/dist/domain/mcpserver/connect.js.map +1 -1
  83. package/dist/domain/mcpserver/controller.d.ts.map +1 -1
  84. package/dist/domain/mcpserver/controller.js +2 -0
  85. package/dist/domain/mcpserver/controller.js.map +1 -1
  86. package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
  87. package/dist/domain/mcpserver/start-connect.js +8 -7
  88. package/dist/domain/mcpserver/start-connect.js.map +1 -1
  89. package/dist/domain/memory/controller.d.ts.map +1 -1
  90. package/dist/domain/memory/controller.js +6 -1
  91. package/dist/domain/memory/controller.js.map +1 -1
  92. package/dist/domain/memory/steps.d.ts +23 -0
  93. package/dist/domain/memory/steps.d.ts.map +1 -1
  94. package/dist/domain/memory/steps.js +35 -0
  95. package/dist/domain/memory/steps.js.map +1 -1
  96. package/dist/domain/oauthapp/controller.d.ts.map +1 -1
  97. package/dist/domain/oauthapp/controller.js +2 -0
  98. package/dist/domain/oauthapp/controller.js.map +1 -1
  99. package/dist/domain/plugin/controller.d.ts.map +1 -1
  100. package/dist/domain/plugin/controller.js +5 -24
  101. package/dist/domain/plugin/controller.js.map +1 -1
  102. package/dist/domain/plugin/overlay/sanitize.js +2 -2
  103. package/dist/domain/schedule/controller.d.ts.map +1 -1
  104. package/dist/domain/schedule/controller.js +7 -1
  105. package/dist/domain/schedule/controller.js.map +1 -1
  106. package/dist/domain/schedule/steps.d.ts +19 -0
  107. package/dist/domain/schedule/steps.d.ts.map +1 -1
  108. package/dist/domain/schedule/steps.js +32 -0
  109. package/dist/domain/schedule/steps.js.map +1 -1
  110. package/dist/domain/session/controller.d.ts +0 -7
  111. package/dist/domain/session/controller.d.ts.map +1 -1
  112. package/dist/domain/session/controller.js +11 -16
  113. package/dist/domain/session/controller.js.map +1 -1
  114. package/dist/domain/session/run-target.d.ts +9 -8
  115. package/dist/domain/session/run-target.d.ts.map +1 -1
  116. package/dist/domain/session/run-target.js.map +1 -1
  117. package/dist/domain/session/steps.d.ts +8 -36
  118. package/dist/domain/session/steps.d.ts.map +1 -1
  119. package/dist/domain/session/steps.js +27 -172
  120. package/dist/domain/session/steps.js.map +1 -1
  121. package/dist/domain/skill/controller.d.ts.map +1 -1
  122. package/dist/domain/skill/controller.js +8 -17
  123. package/dist/domain/skill/controller.js.map +1 -1
  124. package/dist/domain/workflow/controller.d.ts.map +1 -1
  125. package/dist/domain/workflow/controller.js +30 -107
  126. package/dist/domain/workflow/controller.js.map +1 -1
  127. package/dist/domain/workflow/version-resolution.d.ts +5 -0
  128. package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
  129. package/dist/domain/workflow/version-resolution.js +113 -0
  130. package/dist/domain/workflow/version-resolution.js.map +1 -0
  131. package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
  132. package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
  133. package/dist/domain/workflowexecution/create-steps.js +4 -3
  134. package/dist/domain/workflowexecution/create-steps.js.map +1 -1
  135. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  136. package/dist/domain/workflowinstance/controller.js +6 -1
  137. package/dist/domain/workflowinstance/controller.js.map +1 -1
  138. package/dist/domain/workflowinstance/steps.d.ts +20 -0
  139. package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
  140. package/dist/domain/workflowinstance/steps.js +39 -0
  141. package/dist/domain/workflowinstance/steps.js.map +1 -1
  142. package/dist/extensions/identity.d.ts +19 -0
  143. package/dist/extensions/identity.d.ts.map +1 -1
  144. package/dist/extensions/identity.js +22 -3
  145. package/dist/extensions/identity.js.map +1 -1
  146. package/dist/extensions/list-read-scope.d.ts +41 -7
  147. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  148. package/dist/extensions/list-read-scope.js +13 -2
  149. package/dist/extensions/list-read-scope.js.map +1 -1
  150. package/dist/pipeline/apiresource-labels.d.ts +2 -2
  151. package/dist/pipeline/apiresource-labels.js +2 -2
  152. package/dist/pipeline/errors.d.ts +2 -2
  153. package/dist/pipeline/errors.js +2 -2
  154. package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
  155. package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
  156. package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
  157. package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
  158. package/dist/pipeline/steps/authorize.d.ts +27 -0
  159. package/dist/pipeline/steps/authorize.d.ts.map +1 -1
  160. package/dist/pipeline/steps/authorize.js +49 -10
  161. package/dist/pipeline/steps/authorize.js.map +1 -1
  162. package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
  163. package/dist/pipeline/steps/guard-memory-capture.js +2 -1
  164. package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
  165. package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
  166. package/dist/pipeline/steps/guard-reserved-labels.js +12 -12
  167. package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
  168. package/dist/pipeline/steps/helpers.d.ts +9 -5
  169. package/dist/pipeline/steps/helpers.d.ts.map +1 -1
  170. package/dist/pipeline/steps/helpers.js +11 -15
  171. package/dist/pipeline/steps/helpers.js.map +1 -1
  172. package/dist/pipeline/steps/version-history.d.ts +11 -0
  173. package/dist/pipeline/steps/version-history.d.ts.map +1 -1
  174. package/dist/pipeline/steps/version-history.js +26 -0
  175. package/dist/pipeline/steps/version-history.js.map +1 -1
  176. package/package.json +6 -6
  177. package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
  178. package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
  179. package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
  180. package/src/authorization/evaluator.ts +7 -0
  181. package/src/authorization/list-read-scope.ts +22 -11
  182. package/src/authorization/model/__tests__/registry.test.ts +2 -0
  183. package/src/authorization/model/organization.ts +2 -0
  184. package/src/boot/compose.ts +0 -1
  185. package/src/boot/inprocess.ts +0 -9
  186. package/src/domain/agent/__tests__/agent.test.ts +0 -68
  187. package/src/domain/agent/controller.ts +11 -36
  188. package/src/domain/agent/steps.ts +1 -60
  189. package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
  190. package/src/domain/agentchannel/controller.ts +19 -0
  191. package/src/domain/agentchannel/steps.ts +40 -0
  192. package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
  193. package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
  194. package/src/domain/agentexecution/controller.ts +6 -10
  195. package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
  196. package/src/domain/agentexecution/create-steps.ts +38 -138
  197. package/src/domain/agentexecution/run-target.ts +7 -3
  198. package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
  199. package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
  200. package/src/domain/agentinstance/controller.ts +24 -5
  201. package/src/domain/agentinstance/steps.ts +82 -8
  202. package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
  203. package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
  204. package/src/domain/agentshare/controller.ts +21 -0
  205. package/src/domain/agentshare/steps.ts +181 -15
  206. package/src/domain/artifact/controller.ts +18 -22
  207. package/src/domain/channelapp/controller.ts +10 -0
  208. package/src/domain/environment/__tests__/personal.test.ts +143 -0
  209. package/src/domain/environment/controller.ts +10 -0
  210. package/src/domain/environment/personal.ts +141 -0
  211. package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
  212. package/src/domain/executioncontext/controller.ts +10 -0
  213. package/src/domain/executioncontext/steps.ts +2 -1
  214. package/src/domain/mcpserver/connect.ts +32 -92
  215. package/src/domain/mcpserver/controller.ts +13 -1
  216. package/src/domain/mcpserver/start-connect.ts +17 -11
  217. package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
  218. package/src/domain/memory/controller.ts +11 -0
  219. package/src/domain/memory/steps.ts +41 -0
  220. package/src/domain/oauthapp/controller.ts +10 -0
  221. package/src/domain/plugin/controller.ts +22 -36
  222. package/src/domain/plugin/overlay/sanitize.ts +2 -2
  223. package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
  224. package/src/domain/schedule/controller.ts +20 -0
  225. package/src/domain/schedule/steps.ts +45 -2
  226. package/src/domain/session/__tests__/run-gate.test.ts +19 -52
  227. package/src/domain/session/__tests__/session.test.ts +27 -203
  228. package/src/domain/session/controller.ts +10 -30
  229. package/src/domain/session/run-target.ts +9 -8
  230. package/src/domain/session/steps.ts +33 -267
  231. package/src/domain/skill/controller.ts +24 -23
  232. package/src/domain/workflow/controller.ts +58 -145
  233. package/src/domain/workflow/version-resolution.ts +166 -0
  234. package/src/domain/workflowexecution/create-steps.ts +4 -3
  235. package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
  236. package/src/domain/workflowinstance/controller.ts +19 -0
  237. package/src/domain/workflowinstance/steps.ts +49 -1
  238. package/src/extensions/__tests__/composed-support.ts +38 -4
  239. package/src/extensions/__tests__/identity.test.ts +44 -1
  240. package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
  241. package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
  242. package/src/extensions/identity.ts +23 -5
  243. package/src/extensions/list-read-scope.ts +47 -7
  244. package/src/pipeline/apiresource-labels.ts +2 -2
  245. package/src/pipeline/errors.ts +2 -2
  246. package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
  247. package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
  248. package/src/pipeline/steps/authorize.ts +70 -13
  249. package/src/pipeline/steps/guard-memory-capture.ts +2 -1
  250. package/src/pipeline/steps/guard-reserved-labels.ts +12 -14
  251. package/src/pipeline/steps/helpers.ts +11 -15
  252. package/src/pipeline/steps/version-history.ts +34 -0
  253. package/dist/domain/agent/defaultagent.d.ts +0 -37
  254. package/dist/domain/agent/defaultagent.d.ts.map +0 -1
  255. package/dist/domain/agent/defaultagent.js +0 -104
  256. package/dist/domain/agent/defaultagent.js.map +0 -1
  257. package/src/domain/agent/defaultagent.ts +0 -135
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The caller's personal environment as a source of DECLARED variables: the
3
+ * one lookup that answers "which of these declared keys does the person
4
+ * have saved" for every lane that runs a tool on the person's behalf.
5
+ *
6
+ * Two lanes need it and must agree on it. The MCP connect lane (the
7
+ * discovery run a person triggers from the console) resolves a server's
8
+ * declared variables here when no one-time runtime_env was supplied; the
9
+ * agent-execution ExecutionContext build resolves a SESSION-level MCP
10
+ * server's declared variables here when the merge chain did not carry them,
11
+ * because a session's own servers ride no agent instance and so have no
12
+ * environment_refs of their own. Before the build learned this rule a key
13
+ * saved through the console's "save for future" reached the connect lane
14
+ * and never the run.
15
+ *
16
+ * Least privilege by construction: only the keys the caller declares are
17
+ * read, one GetSecretValue per key; nothing here layers a whole
18
+ * environment onto anything. What a missing personal environment or a
19
+ * missing required key MEANS is the caller's to decide — connect refuses
20
+ * (a person asked to connect and cannot without the credential), the
21
+ * build warns (the run fails at the tool with a clearer error) — so both
22
+ * outcomes are returned as values, never thrown. A failing list read is
23
+ * the one infrastructure fault, thrown as Internal.
24
+ *
25
+ * Proven by __tests__/personal.test.ts; the connect lane's wire copy over
26
+ * these outcomes by mcpserver/__tests__/connect.test.ts.
27
+ */
28
+ import { create } from "@bufbuild/protobuf";
29
+ import type { MessageInitShape } from "@bufbuild/protobuf";
30
+
31
+ import type { EnvironmentList } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
32
+ import type {
33
+ EnvironmentSecretValueInputSchema,
34
+ ListEnvironmentsRequestSchema,
35
+ } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
36
+ import type {
37
+ EnvVarDeclaration,
38
+ EnvironmentValue,
39
+ } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
40
+ import type { ExecutionValue } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
41
+ import { ExecutionValueSchema } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
42
+
43
+ import type { Logger } from "../../boot/logger.js";
44
+ import { internalError } from "../../pipeline/errors.js";
45
+ import { PERSONAL_LABEL_KEY, PERSONAL_LABEL_VALUE } from "./constants.js";
46
+
47
+ /**
48
+ * The narrow environment read surface this lookup consumes: the list RPC
49
+ * (to find the personal environment by its label) and the secret read
50
+ * (decrypted; the ordinary Environment surface redacts, oss#405). Both
51
+ * consuming domains' in-process edges already satisfy it.
52
+ */
53
+ export interface PersonalEnvironmentReader {
54
+ list(
55
+ request: MessageInitShape<typeof ListEnvironmentsRequestSchema>,
56
+ ): Promise<EnvironmentList>;
57
+ getSecretValue(
58
+ input: MessageInitShape<typeof EnvironmentSecretValueInputSchema>,
59
+ ): Promise<EnvironmentValue>;
60
+ }
61
+
62
+ /** What the lookup found; the caller decides what each outcome means. */
63
+ export type PersonalEnvironmentResolution =
64
+ | { readonly kind: "no-personal-environment" }
65
+ | {
66
+ readonly kind: "resolved";
67
+ /** The declared keys the personal environment held, as execution values. */
68
+ readonly values: { readonly [key: string]: ExecutionValue };
69
+ /**
70
+ * The REQUIRED declared keys the personal environment did not hold
71
+ * (absent, unreadable, or empty). Optional keys never appear here.
72
+ */
73
+ readonly missing: readonly string[];
74
+ };
75
+
76
+ /**
77
+ * Resolves the given declarations against the caller's personal
78
+ * environment in `org`. A declaration's `is_secret` rides onto the value;
79
+ * a declared key the environment lacks is skipped when optional and
80
+ * reported in `missing` when required. A secret read that fails is logged
81
+ * and treated as absent, the same way for both lanes.
82
+ */
83
+ export async function resolveDeclaredFromPersonalEnvironment(
84
+ reader: PersonalEnvironmentReader,
85
+ logger: Logger,
86
+ org: string,
87
+ declarations: { readonly [key: string]: EnvVarDeclaration },
88
+ ): Promise<PersonalEnvironmentResolution> {
89
+ let listResponse: EnvironmentList;
90
+ try {
91
+ listResponse = await reader.list({
92
+ org,
93
+ labels: { [PERSONAL_LABEL_KEY]: PERSONAL_LABEL_VALUE },
94
+ });
95
+ } catch (error) {
96
+ throw internalError(error, "failed to list personal environments");
97
+ }
98
+ if (listResponse.totalCount === 0 || listResponse.items.length === 0) {
99
+ return { kind: "no-personal-environment" };
100
+ }
101
+
102
+ const personalEnv = listResponse.items[0];
103
+ const personalEnvId = personalEnv?.metadata?.id ?? "";
104
+ // The stored keys are present even when their values are redacted, so
105
+ // existence is checkable before the secret read. Own-key membership
106
+ // (never the prototype chain): an exotic key name must not read as held.
107
+ const stored = personalEnv?.spec?.data ?? {};
108
+
109
+ const values: { [key: string]: ExecutionValue } = {};
110
+ const missing: string[] = [];
111
+ for (const [key, decl] of Object.entries(declarations)) {
112
+ if (!Object.hasOwn(stored, key)) {
113
+ if (!decl.optional) missing.push(key);
114
+ continue;
115
+ }
116
+ let secretValue: EnvironmentValue;
117
+ try {
118
+ secretValue = await reader.getSecretValue({
119
+ environmentId: personalEnvId,
120
+ key,
121
+ });
122
+ } catch (error) {
123
+ logger.warn("Failed to get secret value from personal environment", {
124
+ key,
125
+ personal_env_id: personalEnvId,
126
+ error: error instanceof Error ? error.message : String(error),
127
+ });
128
+ if (!decl.optional) missing.push(key);
129
+ continue;
130
+ }
131
+ if (secretValue.value === "") {
132
+ if (!decl.optional) missing.push(key);
133
+ continue;
134
+ }
135
+ values[key] = create(ExecutionValueSchema, {
136
+ value: secretValue.value,
137
+ isSecret: decl.isSecret,
138
+ });
139
+ }
140
+ return { kind: "resolved", values, missing };
141
+ }
@@ -277,6 +277,7 @@ describe("executioncontext domain (encryption + runner auth enabled)", () => {
277
277
  () =>
278
278
  ts.query.getByReference({
279
279
  kind: ApiResourceKind.execution_context,
280
+ org: created.metadata!.org,
280
281
  slug: created.metadata!.slug,
281
282
  }),
282
283
  ],
@@ -56,6 +56,10 @@ import { newPipeline } from "../../pipeline/pipeline.js";
56
56
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
57
57
  import { RequestContext } from "../../pipeline/request-context.js";
58
58
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
59
+ import {
60
+ loadedTargetAsMethod,
61
+ newAuthorizeResolvedTargetStep,
62
+ } from "../../pipeline/steps/authorize-resolved-target.js";
59
63
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
60
64
  import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
61
65
  import {
@@ -361,6 +365,12 @@ async function getByReference(
361
365
  )
362
366
  .addStep(newValidateProtoStep())
363
367
  .addStep(newLoadByReferenceStep(deps.store, ExecutionContextSchema))
368
+ .addStep(
369
+ newAuthorizeResolvedTargetStep(
370
+ deps.authorizer,
371
+ loadedTargetAsMethod(ExecutionContextQueryController.method.get),
372
+ ),
373
+ )
364
374
  .build()
365
375
  .execute(reqCtx);
366
376
  const ec = reqCtx.get(TARGET_RESOURCE_KEY) as ExecutionContext;
@@ -34,6 +34,7 @@ import { isCiphertextShaped } from "../../encryption/encryption.js";
34
34
  import type { SecretService } from "../../encryption/encryption.js";
35
35
  import { EncryptionScope } from "../../encryption/encryption.js";
36
36
  import type { Authorizer, AuthzDecision } from "../../extensions/authorizer.js";
37
+ import { isServerComposedRequest } from "../../extensions/identity.js";
37
38
  import {
38
39
  internalError,
39
40
  invalidArgumentError,
@@ -240,7 +241,7 @@ export function newAuthorizeExecutionContextCreateStep(
240
241
  ctx: RequestContext<typeof ExecutionContextSchema>,
241
242
  ): Promise<void> {
242
243
  const caller = ctx.callerIdentity;
243
- if (caller.callerClass === "internal" || caller.origin === "in-process") {
244
+ if (isServerComposedRequest(caller)) {
244
245
  return; // authorized upstream — the Java in-process trust arm
245
246
  }
246
247
  let decision: AuthzDecision;
@@ -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
@@ -256,13 +254,6 @@ export async function connect(
256
254
  input: ConnectInput,
257
255
  identity: CallerIdentity,
258
256
  ): Promise<McpServer> {
259
- const engineState = deps.engineState();
260
- if (!engineState.connected) {
261
- throw failedPreconditionError(
262
- "connect is not available: Temporal not configured",
263
- );
264
- }
265
-
266
257
  const mcpServerId = input.mcpServerId;
267
258
  if (mcpServerId === "") {
268
259
  throw invalidArgumentError("mcp_server_id is required");
@@ -282,9 +273,10 @@ export async function connect(
282
273
  throw notFoundError("mcp_server", mcpServerId);
283
274
  }
284
275
 
285
- // The annotation's can_connect check AFTER the load — the Java
286
- // McpServerConnectHandler order (load-before-authorize, stigmer#224).
287
- // C2 Stage 4.
276
+ // The annotation's can_connect check after the load and BEFORE the engine
277
+ // check (the Java McpServerConnectHandler load-before-authorize order,
278
+ // stigmer#224): a caller who may not connect this server learns nothing
279
+ // about whether the engine is up.
288
280
  await authorizeDirect(
289
281
  McpServerCommandController.method.connect,
290
282
  deps.authorizer,
@@ -292,6 +284,13 @@ export async function connect(
292
284
  input,
293
285
  );
294
286
 
287
+ const engineState = deps.engineState();
288
+ if (!engineState.connected) {
289
+ throw failedPreconditionError(
290
+ "connect is not available: Temporal not configured",
291
+ );
292
+ }
293
+
295
294
  const prepared = await prepareConnect(deps, mcpServer, input, identity);
296
295
 
297
296
  try {
@@ -687,34 +686,29 @@ async function resolveOAuthVarsFromManagedEnv(
687
686
  }
688
687
 
689
688
  /**
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.
689
+ * Reads a server's declared variables from the user's personal environment
690
+ * (Go resolveFromPersonalEnvironment) through the shared lookup in
691
+ * domain/environment/personal.ts, and gives its outcomes the connect
692
+ * lane's meaning: a person asked to connect a server and cannot without
693
+ * the credential, so a missing personal environment and a missing required
694
+ * key both refuse with FailedPrecondition. Optional variables are included
695
+ * when available and silently skipped when missing.
695
696
  */
696
697
  async function resolveFromPersonalEnvironment(
697
698
  deps: McpServerConnectDeps,
698
699
  org: string,
699
700
  envDecls: { [key: string]: EnvVarDeclaration },
700
701
  ): 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) {
702
+ const resolution = await resolveDeclaredFromPersonalEnvironment(
703
+ deps.environmentReader,
704
+ deps.logger,
705
+ org,
706
+ envDecls,
707
+ );
708
+ if (resolution.kind === "no-personal-environment") {
709
+ const requiredKeys = Object.entries(envDecls)
710
+ .filter(([, decl]) => !decl.optional)
711
+ .map(([key]) => key);
718
712
  if (requiredKeys.length === 0) {
719
713
  return {};
720
714
  }
@@ -723,66 +717,12 @@ async function resolveFromPersonalEnvironment(
723
717
  `personal environment not found for org '${org}'; save required credentials first: [${requiredKeys.join(" ")}]`,
724
718
  );
725
719
  }
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) {
720
+ if (resolution.missing.length > 0) {
780
721
  throw failedPreconditionError(
781
- `missing required credentials in personal environment: [${missing.join(" ")}]`,
722
+ `missing required credentials in personal environment: [${resolution.missing.join(" ")}]`,
782
723
  );
783
724
  }
784
-
785
- return result;
725
+ return { ...resolution.values };
786
726
  }
787
727
 
788
728
  /**
@@ -53,6 +53,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
53
53
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
54
54
  import { RequestContext } from "../../pipeline/request-context.js";
55
55
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
56
+ import {
57
+ loadedTargetAsMethod,
58
+ newAuthorizeResolvedTargetStep,
59
+ } from "../../pipeline/steps/authorize-resolved-target.js";
56
60
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
57
61
  import {
58
62
  newAuthorizeVisibilityTransitionStep,
@@ -207,7 +211,9 @@ function kindOf(ctx: HandlerContext): ApiResourceKind {
207
211
  }
208
212
 
209
213
  /** The endpoint-auth step's slice of the deps: the guarded fetch the connect slice holds. */
210
- function endpointAuthDeps(deps: McpServerControllerDeps): CompleteEndpointAuthDeps {
214
+ function endpointAuthDeps(
215
+ deps: McpServerControllerDeps,
216
+ ): CompleteEndpointAuthDeps {
211
217
  return { outboundFetch: deps.connect.outboundFetch, logger: deps.logger };
212
218
  }
213
219
 
@@ -629,6 +635,12 @@ async function getByReference(
629
635
  )
630
636
  .addStep(newValidateProtoStep())
631
637
  .addStep(newLoadByReferenceStep(deps.store, McpServerSchema))
638
+ .addStep(
639
+ newAuthorizeResolvedTargetStep(
640
+ deps.authorizer,
641
+ loadedTargetAsMethod(McpServerQueryController.method.get),
642
+ ),
643
+ )
632
644
  .addStep(newEnrichOAuthStatusStep(deps.store, deps.logger))
633
645
  .build()
634
646
  .execute(reqCtx);
@@ -69,13 +69,6 @@ export async function startConnect(
69
69
  input: ConnectInput,
70
70
  identity: CallerIdentity,
71
71
  ): Promise<McpServer> {
72
- const engineState = deps.engineState();
73
- if (!engineState.connected) {
74
- throw failedPreconditionError(
75
- "connect is not available: Temporal not configured",
76
- );
77
- }
78
-
79
72
  const mcpServerId = input.mcpServerId;
80
73
  if (mcpServerId === "") {
81
74
  throw invalidArgumentError("mcp_server_id is required");
@@ -95,9 +88,10 @@ export async function startConnect(
95
88
  throw notFoundError("mcp_server", mcpServerId);
96
89
  }
97
90
 
98
- // The annotation's can_connect check AFTER the load — the Java
99
- // McpServerStartConnectHandler order (the connect handler's shared
100
- // load/authorize steps, stigmer#224). C2 Stage 4.
91
+ // The annotation's can_connect check after the load and BEFORE the engine
92
+ // check (the Java McpServerStartConnectHandler load-before-authorize order,
93
+ // stigmer#224): a caller who may not connect this server learns nothing
94
+ // about whether the engine is up.
101
95
  await authorizeDirect(
102
96
  McpServerCommandController.method.startConnect,
103
97
  deps.authorizer,
@@ -105,6 +99,13 @@ export async function startConnect(
105
99
  input,
106
100
  );
107
101
 
102
+ const engineState = deps.engineState();
103
+ if (!engineState.connected) {
104
+ throw failedPreconditionError(
105
+ "connect is not available: Temporal not configured",
106
+ );
107
+ }
108
+
108
109
  const connectStatus = mcpServer.status?.connectStatus;
109
110
  if (connectStatus?.phase === ConnectPhase.connecting) {
110
111
  if (
@@ -249,7 +250,12 @@ async function settleConnectAsync(
249
250
  outcome.failure,
250
251
  ASYNC_CONNECT_TIMEOUT,
251
252
  );
252
- await persistConnectFailure(deps.store, deps.logger, mcpServerId, failure);
253
+ await persistConnectFailure(
254
+ deps.store,
255
+ deps.logger,
256
+ mcpServerId,
257
+ failure,
258
+ );
253
259
  return;
254
260
  }
255
261
 
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pins the create lane's authorization question (steps.ts,
3
+ * resolveMemoryCreateTargets): a wire caller asks can_create_session on
4
+ * metadata.org — whoever may converse in an organization may remember
5
+ * there — and a server-composed request (internal, or any identity that
6
+ * entered in-process) asks nothing, as the capture gate before it already
7
+ * says.
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { create } from "@bufbuild/protobuf";
11
+
12
+ import { MemorySchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
13
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
14
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
15
+
16
+ import type { CallerIdentity } from "../../../extensions/identity.js";
17
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
18
+ import { RequestContext } from "../../../pipeline/request-context.js";
19
+ import {
20
+ MEMORY_CREATE_DENIED_MESSAGE,
21
+ resolveMemoryCreateTargets,
22
+ } from "../steps.js";
23
+
24
+ function ctxFor(caller: CallerIdentity) {
25
+ return new RequestContext(
26
+ MemorySchema,
27
+ create(MemorySchema, { metadata: { name: "prefers-tea", org: "acme" } }),
28
+ caller,
29
+ ApiResourceKind.memory,
30
+ );
31
+ }
32
+
33
+ describe("resolveMemoryCreateTargets", () => {
34
+ it("a wire caller asks can_create_session on the memory's organization", () => {
35
+ expect(resolveMemoryCreateTargets(ctxFor(testCallerIdentity()))).toEqual([
36
+ {
37
+ permission: IamPermission.can_create_session,
38
+ resourceKind: ApiResourceKind.organization,
39
+ resourceId: "acme",
40
+ deniedMessage: MEMORY_CREATE_DENIED_MESSAGE,
41
+ },
42
+ ]);
43
+ });
44
+
45
+ it("a server-composed request asks nothing", () => {
46
+ expect(
47
+ resolveMemoryCreateTargets(
48
+ ctxFor(testCallerIdentity({ callerClass: "internal" })),
49
+ ),
50
+ ).toEqual([]);
51
+ expect(
52
+ resolveMemoryCreateTargets(
53
+ ctxFor(
54
+ testCallerIdentity({ callerClass: "user", origin: "in-process" }),
55
+ ),
56
+ ),
57
+ ).toEqual([]);
58
+ });
59
+ });
@@ -77,6 +77,7 @@ import { newPipeline } from "../../pipeline/pipeline.js";
77
77
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
78
78
  import { RequestContext } from "../../pipeline/request-context.js";
79
79
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
80
+ import { newAuthorizeResolvedTargetStep } from "../../pipeline/steps/authorize-resolved-target.js";
80
81
  import { newGuardMemoryCaptureStep } from "../../pipeline/steps/guard-memory-capture.js";
81
82
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
82
83
  import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
@@ -118,6 +119,7 @@ import {
118
119
  newResolveMemoryDefaultsStep,
119
120
  newTransitionMemoryLifecycleStep,
120
121
  newValidateMemoryUpdateStep,
122
+ resolveMemoryCreateTargets,
121
123
  } from "./steps.js";
122
124
 
123
125
  export interface MemoryControllerDeps {
@@ -194,6 +196,15 @@ async function createMemory(
194
196
  .addStep(newGuardMemoryCaptureStep(deps.runnerCredentialProvider))
195
197
  .addStep(newValidateVisibilityStep())
196
198
  .addStep(newResolveMemoryDefaultsStep())
199
+ // Before the enablement and cap checks, so a refused caller learns
200
+ // nothing about the organization's settings (steps.ts,
201
+ // resolveMemoryCreateTargets).
202
+ .addStep(
203
+ newAuthorizeResolvedTargetStep(
204
+ deps.authorizer,
205
+ resolveMemoryCreateTargets,
206
+ ),
207
+ )
197
208
  .addStep(newCheckMemoryEnablementStep(deps.store))
198
209
  .addStep(newCheckMemoryCapStep(deps.store))
199
210
  .addStep(newResolveSlugStep())
@@ -30,6 +30,7 @@ import { MemoryListSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/i
30
30
  import { MemoryProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/spec_pb";
31
31
  import { MemoryStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/status_pb";
32
32
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
33
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
33
34
  import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
34
35
 
35
36
  import {
@@ -42,6 +43,8 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
42
43
  import type { ListReadScope } from "../../extensions/list-read-scope.js";
43
44
  import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
44
45
  import type { RequestContext } from "../../pipeline/request-context.js";
46
+ import { isServerComposedRequest } from "../../extensions/identity.js";
47
+ import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
45
48
  import {
46
49
  generateId,
47
50
  setAuditFieldsForUpdate,
@@ -159,6 +162,44 @@ export function newResolveMemoryDefaultsStep(): PipelineStep<
159
162
  };
160
163
  }
161
164
 
165
+ /** The deny copy of the create lane's organization bar. */
166
+ export const MEMORY_CREATE_DENIED_MESSAGE =
167
+ "unauthorized to capture memory in this organization";
168
+
169
+ /**
170
+ * The create lane's authorization question, for AuthorizeResolvedTarget
171
+ * after ResolveMemoryDefaults (which requires metadata.org) and BEFORE
172
+ * CheckMemoryEnablement, so a refused caller learns nothing about the
173
+ * organization's memory settings. The RPC is is_skip_authorization
174
+ * because the row does not exist yet and the subject IS the caller
175
+ * (command.proto); GuardMemoryCapture is the gate that decides WHO may
176
+ * capture at all, and this question is WHERE: can_create_session on
177
+ * metadata.org — memory is captured inside a conversation, so whoever may
178
+ * converse in an organization may remember there.
179
+ *
180
+ * The credentialed capture lane is already scoped to the run's
181
+ * organization by the credential provider (a mismatch is refused inside
182
+ * GuardMemoryCapture with its own copy); this question closes the plain
183
+ * path, a person with no run credential naming an organization they hold
184
+ * nothing in. A server-composed traversal asks nothing, as the gate itself
185
+ * says: the entry-point request already passed.
186
+ */
187
+ export function resolveMemoryCreateTargets(
188
+ ctx: RequestContext<typeof MemorySchema>,
189
+ ): ReadonlyArray<AuthorizationTarget> {
190
+ if (isServerComposedRequest(ctx.callerIdentity)) {
191
+ return [];
192
+ }
193
+ return [
194
+ {
195
+ permission: IamPermission.can_create_session,
196
+ resourceKind: ApiResourceKind.organization,
197
+ resourceId: ctx.newState.metadata?.org ?? "",
198
+ deniedMessage: MEMORY_CREATE_DENIED_MESSAGE,
199
+ },
200
+ ];
201
+ }
202
+
162
203
  /**
163
204
  * CheckMemoryEnablement — Go checkMemoryEnablementStep: enforces the
164
205
  * org's memory_enabled switch at write time, FAIL-CLOSED (DD-005 D2): a
@@ -52,6 +52,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
52
52
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
53
53
  import { RequestContext } from "../../pipeline/request-context.js";
54
54
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
55
+ import {
56
+ loadedTargetAsMethod,
57
+ newAuthorizeResolvedTargetStep,
58
+ } from "../../pipeline/steps/authorize-resolved-target.js";
55
59
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
56
60
  import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
57
61
  import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
@@ -362,6 +366,12 @@ async function getByReference(
362
366
  )
363
367
  .addStep(newValidateProtoStep())
364
368
  .addStep(newLoadByReferenceStep(deps.store, OAuthAppSchema))
369
+ .addStep(
370
+ newAuthorizeResolvedTargetStep(
371
+ deps.authorizer,
372
+ loadedTargetAsMethod(OAuthAppQueryController.method.get),
373
+ ),
374
+ )
365
375
  .build()
366
376
  .execute(reqCtx);
367
377
  const app = reqCtx.get(TARGET_RESOURCE_KEY) as OAuthApp;