@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
@@ -35,6 +35,7 @@ import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb"
35
35
  import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
36
36
  import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
37
37
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
38
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
38
39
 
39
40
  import {
40
41
  failedPreconditionError,
@@ -44,6 +45,13 @@ import {
44
45
  } from "../../pipeline/errors.js";
45
46
  import type { PipelineStep } from "../../pipeline/pipeline.js";
46
47
  import type { RequestContext } from "../../pipeline/request-context.js";
48
+ import type { Authorizer } from "../../extensions/authorizer.js";
49
+ import {
50
+ AUTHORIZATION_UNAVAILABLE_MESSAGE,
51
+ evaluateAuthorizer,
52
+ } from "../../pipeline/steps/authorize.js";
53
+ import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
54
+ import type { GetByReferenceDesc } from "../../pipeline/steps/authorize-resolved-target.js";
47
55
  import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
48
56
  import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
49
57
  import type { Store } from "../../store/interface.js";
@@ -61,10 +69,66 @@ type AgentShareDesc = typeof AgentShareSchema;
61
69
  /**
62
70
  * Context key for the agent resolved from spec.agent_ref during
63
71
  * create/apply, so later steps never re-load it — Go referencedAgentKey.
64
- * Module-private: only ResolveShareDefaults writes it and StampAgentPin
65
- * reads it, both in this file.
72
+ * ResolveShareDefaults writes it; the create lane's authorization step and
73
+ * StampAgentPin read it.
66
74
  */
67
- const REFERENCED_AGENT_KEY = "agentShareReferencedAgent";
75
+ export const REFERENCED_AGENT_KEY = "agentShareReferencedAgent";
76
+
77
+ /** The deny copy of the agent's bar, the Java AgentShareCreateHandler's. */
78
+ export const SHARE_AGENT_DENIED_MESSAGE =
79
+ "You don't have permission to share this agent";
80
+
81
+ /** The deny copy of the sharing organization's bar, the Java handler's. */
82
+ export const SHARE_ORGANIZATION_DENIED_MESSAGE =
83
+ "You don't have permission to create agent shares in this organization";
84
+
85
+ /**
86
+ * The create lane's authorization questions, for AuthorizeResolvedTarget
87
+ * after ResolveShareDefaults: the two-arm consent bar the Java handler
88
+ * enforced and the model's comment on can_create_agent_share describes.
89
+ * The RPC is is_skip_authorization because the target is the referenced
90
+ * agent, resolved from a slug, not a request field.
91
+ *
92
+ * A same-org share (spec.agent_ref.org equals metadata.org, after the
93
+ * resolve step made the reference absolute) asks can_edit on the agent:
94
+ * sharing puts the agent in front of a wider audience, an editor's act.
95
+ *
96
+ * A cross-org share of another organization's public agent asks two
97
+ * questions, agent first as Java did: can_execute on the agent (whoever may
98
+ * run it may offer it) and then can_create_agent_share on the SHARING
99
+ * organization — a public share spends that organization's credits on the
100
+ * open internet, an admin-level act. The cross-org validity rules (public
101
+ * agent, no org audience, public dependencies) stay the resolve step's.
102
+ */
103
+ export function resolveShareCreateTargets(
104
+ ctx: RequestContext<AgentShareDesc>,
105
+ ): ReadonlyArray<AuthorizationTarget> {
106
+ const agent = ctx.get(REFERENCED_AGENT_KEY) as Agent | undefined;
107
+ if (agent === undefined) {
108
+ throw new Error("referenced agent not found in context");
109
+ }
110
+ const share = ctx.newState;
111
+ const shareOrg = share.metadata?.org ?? "";
112
+ const crossOrg = (share.spec?.agentRef?.org ?? "") !== shareOrg;
113
+ const agentBar: AuthorizationTarget = {
114
+ permission: crossOrg ? IamPermission.can_execute : IamPermission.can_edit,
115
+ resourceKind: ApiResourceKind.agent,
116
+ resourceId: agent.metadata?.id ?? "",
117
+ deniedMessage: SHARE_AGENT_DENIED_MESSAGE,
118
+ };
119
+ if (!crossOrg) {
120
+ return [agentBar];
121
+ }
122
+ return [
123
+ agentBar,
124
+ {
125
+ permission: IamPermission.can_create_agent_share,
126
+ resourceKind: ApiResourceKind.organization,
127
+ resourceId: shareOrg,
128
+ deniedMessage: SHARE_ORGANIZATION_DENIED_MESSAGE,
129
+ },
130
+ ];
131
+ }
68
132
 
69
133
  /**
70
134
  * The single refusal for every anonymous/member resolution miss: share
@@ -78,6 +142,71 @@ export function sharedNotFound(slug: string): ConnectError {
78
142
  return notFoundError("Agent", slug);
79
143
  }
80
144
 
145
+ /**
146
+ * AuthorizeMemberAudience — the member-profile lane's gate, membership
147
+ * BEFORE existence: the Java AgentShareGetSharedProfileForMemberHandler
148
+ * order. getSharedProfileForMember is the signed-in resolution path for a
149
+ * share URL, and the proto pins its contract: a share that does not exist,
150
+ * is disabled, or is asked for by someone who is not a member of the
151
+ * sharing organization all answer the SAME NOT_FOUND, so a share URL
152
+ * teaches a non-member nothing — not even whether the share exists. A
153
+ * non-member resolving a public share uses the anonymous lane instead.
154
+ *
155
+ * The question is can_view on the organization named in the reference (the
156
+ * viewer set: members and above), asked live on every call so a revoked
157
+ * member loses access at once; the RPC is is_skip_authorization because
158
+ * the target is the reference's organization, not a request field the
159
+ * annotation can key on. This is deliberately NOT the shared
160
+ * AuthorizeResolvedTarget step: that step answers deny with
161
+ * PERMISSION_DENIED, and this lane's wire contract is the indistinguishable
162
+ * NOT_FOUND above, so the lane keeps its own mapping over the same
163
+ * evaluation (the decision form checkMyPermission uses). An empty org is
164
+ * left to the loader's INVALID_ARGUMENT, the proto's own copy for it; an
165
+ * unavailable authorizer is INTERNAL, never softened into a refusal; the
166
+ * `internal` class is exempt as everywhere.
167
+ */
168
+ export function newAuthorizeMemberAudienceStep(
169
+ authorizer: Authorizer,
170
+ ): PipelineStep<GetByReferenceDesc> {
171
+ return {
172
+ name: "AuthorizeMemberAudience",
173
+ async execute(ctx: RequestContext<GetByReferenceDesc>): Promise<void> {
174
+ const ref = ctx.input;
175
+ if (ref.org === "" || ctx.callerIdentity.callerClass === "internal") {
176
+ return;
177
+ }
178
+ const decision = await evaluateAuthorizer(
179
+ authorizer,
180
+ ctx.callerIdentity,
181
+ {
182
+ permission: IamPermission.can_view,
183
+ resourceKind: ApiResourceKind.organization,
184
+ resourceId: ref.org,
185
+ },
186
+ );
187
+ switch (decision.kind) {
188
+ case "allow":
189
+ return;
190
+ case "deny":
191
+ case "not-found":
192
+ throw sharedNotFound(ref.slug);
193
+ case "unavailable":
194
+ throw internalError(
195
+ decision.cause,
196
+ AUTHORIZATION_UNAVAILABLE_MESSAGE,
197
+ );
198
+ default: {
199
+ const exhaustive: never = decision;
200
+ throw internalError(
201
+ new Error(`unknown decision ${JSON.stringify(exhaustive)}`),
202
+ AUTHORIZATION_UNAVAILABLE_MESSAGE,
203
+ );
204
+ }
205
+ }
206
+ },
207
+ };
208
+ }
209
+
81
210
  /**
82
211
  * The link-token predicate — Go sharingLinkTokenAllowed, the mirror of the
83
212
  * cloud edition's SharingLinkTokenPolicy:
@@ -90,7 +219,10 @@ export function sharedNotFound(slug: string): ConnectError {
90
219
  * guard restores Go's semantics; the length of a token is not secret (all
91
220
  * server-minted tokens are 27 chars).
92
221
  */
93
- export function sharingLinkTokenAllowed(presented: string, live: string): boolean {
222
+ export function sharingLinkTokenAllowed(
223
+ presented: string,
224
+ live: string,
225
+ ): boolean {
94
226
  if (live === "") {
95
227
  return true;
96
228
  }
@@ -182,7 +314,9 @@ export async function findShareByOrgAndSlug(
182
314
  * provided neither — the canonical share keeps the agent's hosted URL.
183
315
  * Runs before ResolveSlug, which skips already-set slugs.
184
316
  */
185
- export function newResolveShareDefaultsStep(store: Store): PipelineStep<AgentShareDesc> {
317
+ export function newResolveShareDefaultsStep(
318
+ store: Store,
319
+ ): PipelineStep<AgentShareDesc> {
186
320
  return {
187
321
  name: "ResolveShareDefaults",
188
322
  async execute(ctx: RequestContext<AgentShareDesc>): Promise<void> {
@@ -204,7 +338,11 @@ export function newResolveShareDefaultsStep(store: Store): PipelineStep<AgentSha
204
338
  agentRef!.org = metadata!.org;
205
339
  }
206
340
 
207
- const agent = await findAgentByOrgAndSlug(store, agentRef!.org, agentRef!.slug);
341
+ const agent = await findAgentByOrgAndSlug(
342
+ store,
343
+ agentRef!.org,
344
+ agentRef!.slug,
345
+ );
208
346
  if (agent === undefined) {
209
347
  throw notFoundError("Agent", agentRef!.slug);
210
348
  }
@@ -288,7 +426,10 @@ async function findNonPublicDependencies(
288
426
 
289
427
  const seen = new Set<string>();
290
428
  const deps: DepRef[] = [];
291
- const add = (kind: ApiResourceKind, ref: ApiResourceReference | undefined): void => {
429
+ const add = (
430
+ kind: ApiResourceKind,
431
+ ref: ApiResourceReference | undefined,
432
+ ): void => {
292
433
  if (ref === undefined || ref.slug === "") {
293
434
  return;
294
435
  }
@@ -320,8 +461,20 @@ async function findNonPublicDependencies(
320
461
  // are the only dependency kinds an agent blueprint declares.
321
462
  const resolved =
322
463
  dep.kind === ApiResourceKind.skill
323
- ? await findResourceBySlug(store, dep.kind, SkillSchema, dep.slug, dep.org)
324
- : await findResourceBySlug(store, dep.kind, McpServerSchema, dep.slug, dep.org);
464
+ ? await findResourceBySlug(
465
+ store,
466
+ dep.kind,
467
+ SkillSchema,
468
+ dep.slug,
469
+ dep.org,
470
+ )
471
+ : await findResourceBySlug(
472
+ store,
473
+ dep.kind,
474
+ McpServerSchema,
475
+ dep.slug,
476
+ dep.org,
477
+ );
325
478
  visibility = resolved?.metadata?.visibility;
326
479
  } catch (error) {
327
480
  throw internalError(
@@ -396,20 +549,28 @@ export function newValidateShareUpdateStep(): PipelineStep<AgentShareDesc> {
396
549
  // Normalize the input ref's org the same way create does (empty
397
550
  // means the share's own org) before comparing.
398
551
  const inputOrg =
399
- (inputRef?.org ?? "") !== "" ? inputRef!.org : (existing.metadata?.org ?? "");
552
+ (inputRef?.org ?? "") !== ""
553
+ ? inputRef!.org
554
+ : (existing.metadata?.org ?? "");
400
555
 
401
556
  if (
402
557
  (inputRef?.slug ?? "") !== (existingRef?.slug ?? "") ||
403
558
  inputOrg !== (existingRef?.org ?? "")
404
559
  ) {
405
560
  throw failedPreconditionError(
406
- agentRefImmutableMessage(existingRef?.org ?? "", existingRef?.slug ?? ""),
561
+ agentRefImmutableMessage(
562
+ existingRef?.org ?? "",
563
+ existingRef?.slug ?? "",
564
+ ),
407
565
  );
408
566
  }
409
567
 
410
- const isCrossOrg = (existingRef?.org ?? "") !== (existing.metadata?.org ?? "");
568
+ const isCrossOrg =
569
+ (existingRef?.org ?? "") !== (existing.metadata?.org ?? "");
411
570
  if (isCrossOrg && ctx.input.spec?.audience === AgentShareAudience.org) {
412
- throw failedPreconditionError(crossOrgAudienceMessage(existingRef?.org ?? ""));
571
+ throw failedPreconditionError(
572
+ crossOrgAudienceMessage(existingRef?.org ?? ""),
573
+ );
413
574
  }
414
575
  },
415
576
  };
@@ -430,7 +591,11 @@ export async function buildSharedAgentProfile(
430
591
  share: AgentShare,
431
592
  ): Promise<SharedAgentProfile> {
432
593
  const ref = share.spec?.agentRef;
433
- const agent = await findAgentByOrgAndSlug(store, ref?.org ?? "", ref?.slug ?? "");
594
+ const agent = await findAgentByOrgAndSlug(
595
+ store,
596
+ ref?.org ?? "",
597
+ ref?.slug ?? "",
598
+ );
434
599
  if (agent === undefined) {
435
600
  throw sharedNotFound(share.metadata?.slug ?? "");
436
601
  }
@@ -440,7 +605,8 @@ export async function buildSharedAgentProfile(
440
605
  throw sharedNotFound(share.metadata?.slug ?? "");
441
606
  }
442
607
 
443
- const isCrossOrg = (share.metadata?.org ?? "") !== (agent.metadata?.org ?? "");
608
+ const isCrossOrg =
609
+ (share.metadata?.org ?? "") !== (agent.metadata?.org ?? "");
444
610
  if (
445
611
  isCrossOrg &&
446
612
  agent.metadata?.visibility !== ApiResourceVisibility.visibility_public
@@ -66,9 +66,9 @@ import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
66
66
  import { RequestContext } from "../../pipeline/request-context.js";
67
67
  import {
68
68
  authorizeDirect,
69
- authorizeResolvedResource,
70
69
  newAuthorizeStep,
71
70
  } from "../../pipeline/steps/authorize.js";
71
+ import { newAuthorizeResolvedTargetStep } from "../../pipeline/steps/authorize-resolved-target.js";
72
72
  import {
73
73
  generateId,
74
74
  setAuditFieldsForCreate,
@@ -426,23 +426,18 @@ async function listByExecution(
426
426
  }
427
427
  },
428
428
  })
429
- .addStep({
430
- name: "AuthorizeParentExecution",
431
- async execute(stepCtx): Promise<void> {
432
- // The Java ArtifactListByExecutionHandler's hand-rolled gate
433
- // (20260830.01 ruling Q8): can_view on the PARENT execution —
434
- // "if you can view an execution, you can list its artifacts."
435
- // The two-field dispatch (workflow-execution first, the Java
436
- // order) is why the declarative annotation cannot express this
437
- // lane and the proto carries is_skip_authorization. Deny copy is
438
- // the Java handler's byte-pinned error_msg; a nonexistent id
439
- // answers NOT_FOUND through the authorizer's probe — the ruled
440
- // uniform unknown-id posture (DD-007 addendum), a recorded
441
- // divergence from Java's deny-everyone arm on this lane.
442
- const input = stepCtx.input;
443
- await authorizeResolvedResource(
444
- deps.authorizer,
445
- stepCtx.callerIdentity,
429
+ // The Java ArtifactListByExecutionHandler's gate: can_view on the PARENT
430
+ // execution — "if you can view an execution, you can list its
431
+ // artifacts." The two-field dispatch (workflow-execution first, the Java
432
+ // order) is why the declarative annotation cannot express this lane and
433
+ // the proto carries is_skip_authorization. Deny copy is the Java
434
+ // handler's byte-pinned error_msg; a nonexistent id answers NOT_FOUND
435
+ // through the authorizer's probe — the ruled uniform unknown-id
436
+ // posture, a recorded divergence from Java's deny-everyone arm here.
437
+ .addStep(
438
+ newAuthorizeResolvedTargetStep(
439
+ deps.authorizer,
440
+ ({ input }) => [
446
441
  {
447
442
  permission: IamPermission.can_view,
448
443
  resourceKind:
@@ -453,11 +448,12 @@ async function listByExecution(
453
448
  input.workflowExecutionId !== ""
454
449
  ? input.workflowExecutionId
455
450
  : input.agentExecutionId,
451
+ deniedMessage: "unauthorized to list artifacts for this execution",
456
452
  },
457
- "unauthorized to list artifacts for this execution",
458
- );
459
- },
460
- })
453
+ ],
454
+ "AuthorizeParentExecution",
455
+ ),
456
+ )
461
457
  .addStep({
462
458
  name: "QueryArtifactsByExecution",
463
459
  async execute(stepCtx): Promise<void> {
@@ -51,6 +51,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
51
51
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
52
52
  import { RequestContext } from "../../pipeline/request-context.js";
53
53
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
54
+ import {
55
+ loadedTargetAsMethod,
56
+ newAuthorizeResolvedTargetStep,
57
+ } from "../../pipeline/steps/authorize-resolved-target.js";
54
58
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
55
59
  import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
56
60
  import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
@@ -369,6 +373,12 @@ async function getByReference(
369
373
  )
370
374
  .addStep(newValidateProtoStep())
371
375
  .addStep(newLoadByReferenceStep(deps.store, ChannelAppSchema))
376
+ .addStep(
377
+ newAuthorizeResolvedTargetStep(
378
+ deps.authorizer,
379
+ loadedTargetAsMethod(ChannelAppQueryController.method.get),
380
+ ),
381
+ )
372
382
  .build()
373
383
  .execute(reqCtx);
374
384
  const app = reqCtx.get(TARGET_RESOURCE_KEY) as ChannelApp;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Pins the shared personal-environment lookup (personal.ts) on the seam
3
+ * both its callers stand on: outcomes are VALUES (no personal environment;
4
+ * resolved with the required keys still missing named), a declaration's
5
+ * is_secret rides onto the value, optional keys are skipped silently in
6
+ * every absent arm, only stored keys are read (existence before the
7
+ * secret read; own-key membership), and a failing list is the one thrown
8
+ * fault. The connect lane's refusals over these outcomes are pinned in
9
+ * mcpserver/__tests__/connect.test.ts; the build's warnings in
10
+ * agentexecution/__tests__/create-execution-context-step.test.ts.
11
+ */
12
+ import { create } from "@bufbuild/protobuf";
13
+ import { Code, ConnectError } from "@connectrpc/connect";
14
+ import { describe, expect, it } from "vitest";
15
+
16
+ import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
17
+ import { EnvironmentListSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
18
+ import {
19
+ EnvVarDeclarationSchema,
20
+ EnvironmentValueSchema,
21
+ } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
22
+
23
+ import { createLogger } from "../../../boot/logger.js";
24
+ import { PERSONAL_LABEL_KEY, PERSONAL_LABEL_VALUE } from "../constants.js";
25
+ import type { PersonalEnvironmentReader } from "../personal.js";
26
+ import { resolveDeclaredFromPersonalEnvironment } from "../personal.js";
27
+
28
+ const silentLogger = createLogger({
29
+ level: "error",
30
+ pretty: false,
31
+ write: () => {},
32
+ });
33
+
34
+ /** A declaration literal as the generated message. */
35
+ function decl(init: { isSecret: boolean; optional?: boolean }) {
36
+ return create(EnvVarDeclarationSchema, init);
37
+ }
38
+
39
+ function readerOver(
40
+ stored: Record<string, string>,
41
+ reads: string[],
42
+ failing: ReadonlySet<string> = new Set(),
43
+ ): PersonalEnvironmentReader {
44
+ return {
45
+ list: async (request) => {
46
+ expect(request.labels).toEqual({ [PERSONAL_LABEL_KEY]: PERSONAL_LABEL_VALUE });
47
+ return create(EnvironmentListSchema, {
48
+ totalCount: 1,
49
+ items: [
50
+ create(EnvironmentSchema, {
51
+ metadata: { id: "env_p", org: "acme", slug: "personal" },
52
+ spec: {
53
+ data: Object.fromEntries(
54
+ Object.keys(stored).map((k) => [k, { value: "***", isSecret: true }]),
55
+ ),
56
+ },
57
+ }),
58
+ ],
59
+ });
60
+ },
61
+ getSecretValue: async (input) => {
62
+ const key = input.key ?? "";
63
+ reads.push(key);
64
+ if (failing.has(key)) {
65
+ throw new ConnectError("decrypt failed", Code.Internal);
66
+ }
67
+ return create(EnvironmentValueSchema, { value: stored[key] ?? "", isSecret: true });
68
+ },
69
+ };
70
+ }
71
+
72
+ describe("resolveDeclaredFromPersonalEnvironment", () => {
73
+ it("answers no-personal-environment as a value when the org has none", async () => {
74
+ const reader: PersonalEnvironmentReader = {
75
+ list: async () => create(EnvironmentListSchema, { totalCount: 0, items: [] }),
76
+ getSecretValue: async () => {
77
+ throw new Error("unreached");
78
+ },
79
+ };
80
+ const out = await resolveDeclaredFromPersonalEnvironment(reader, silentLogger, "acme", {
81
+ TOKEN: decl({ isSecret: true }),
82
+ });
83
+ expect(out).toEqual({ kind: "no-personal-environment" });
84
+ });
85
+
86
+ it("resolves stored keys with the declaration's is_secret, names missing required keys, skips optional ones", async () => {
87
+ const reads: string[] = [];
88
+ const reader = readerOver({ TOKEN: "t-1", PUBLIC_URL: "https://x", EMPTY: "" }, reads);
89
+ const out = await resolveDeclaredFromPersonalEnvironment(reader, silentLogger, "acme", {
90
+ TOKEN: decl({ isSecret: true }),
91
+ PUBLIC_URL: decl({ isSecret: false }),
92
+ EMPTY: decl({ isSecret: true }),
93
+ ABSENT_REQUIRED: decl({ isSecret: true }),
94
+ ABSENT_OPTIONAL: decl({ isSecret: true, optional: true }),
95
+ });
96
+ expect(out.kind).toBe("resolved");
97
+ if (out.kind !== "resolved") return;
98
+ expect(out.values["TOKEN"]?.value).toBe("t-1");
99
+ expect(out.values["TOKEN"]?.isSecret).toBe(true);
100
+ expect(out.values["PUBLIC_URL"]?.value).toBe("https://x");
101
+ expect(out.values["PUBLIC_URL"]?.isSecret).toBe(false);
102
+ // An empty stored value is absent; required → missing.
103
+ expect(out.values["EMPTY"]).toBeUndefined();
104
+ expect([...out.missing].sort()).toEqual(["ABSENT_REQUIRED", "EMPTY"]);
105
+ // Only stored keys are read: absent declarations cost no secret read.
106
+ expect(reads.sort()).toEqual(["EMPTY", "PUBLIC_URL", "TOKEN"]);
107
+ });
108
+
109
+ it("treats a failing secret read as absent: missing when required, skipped when optional", async () => {
110
+ const reads: string[] = [];
111
+ const reader = readerOver({ A: "a", B: "b" }, reads, new Set(["A", "B"]));
112
+ const out = await resolveDeclaredFromPersonalEnvironment(reader, silentLogger, "acme", {
113
+ A: decl({ isSecret: true }),
114
+ B: decl({ isSecret: true, optional: true }),
115
+ });
116
+ expect(out).toEqual({ kind: "resolved", values: {}, missing: ["A"] });
117
+ });
118
+
119
+ it("never reads a prototype name as a stored key", async () => {
120
+ const reads: string[] = [];
121
+ const reader = readerOver({}, reads);
122
+ const out = await resolveDeclaredFromPersonalEnvironment(reader, silentLogger, "acme", {
123
+ constructor: decl({ isSecret: true }),
124
+ toString: decl({ isSecret: true, optional: true }),
125
+ });
126
+ expect(out).toEqual({ kind: "resolved", values: {}, missing: ["constructor"] });
127
+ expect(reads).toEqual([]);
128
+ });
129
+
130
+ it("throws Internal when the personal environment cannot be listed", async () => {
131
+ const reader: PersonalEnvironmentReader = {
132
+ list: async () => {
133
+ throw new Error("store offline");
134
+ },
135
+ getSecretValue: async () => {
136
+ throw new Error("unreached");
137
+ },
138
+ };
139
+ await expect(
140
+ resolveDeclaredFromPersonalEnvironment(reader, silentLogger, "acme", { A: decl({ isSecret: true }) }),
141
+ ).rejects.toMatchObject({ code: Code.Internal });
142
+ });
143
+ });
@@ -58,6 +58,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
58
58
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
59
59
  import { RequestContext } from "../../pipeline/request-context.js";
60
60
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
61
+ import {
62
+ loadedTargetAsMethod,
63
+ newAuthorizeResolvedTargetStep,
64
+ } from "../../pipeline/steps/authorize-resolved-target.js";
61
65
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
62
66
  import { newAuthorizeVisibilityTransitionStep } from "../../pipeline/steps/visibility-gates.js";
63
67
  import {
@@ -682,6 +686,12 @@ async function getByReference(
682
686
  )
683
687
  .addStep(newValidateProtoStep())
684
688
  .addStep(newLoadByReferenceStep(deps.store, EnvironmentSchema))
689
+ .addStep(
690
+ newAuthorizeResolvedTargetStep(
691
+ deps.authorizer,
692
+ loadedTargetAsMethod(EnvironmentQueryController.method.get),
693
+ ),
694
+ )
685
695
  .build()
686
696
  .execute(reqCtx);
687
697
  const env = reqCtx.get(TARGET_RESOURCE_KEY) as Environment;