@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
@@ -10,13 +10,13 @@
10
10
  * Proven by agentinstance.conformance.test.ts (CONFORMANCE_TARGET=local)
11
11
  * and __tests__/agentinstance.test.ts.
12
12
  *
13
- * Versus Stigmer Cloud, OSS excludes the Authorize (FGA
14
- * can_create_instance on the parent agent), CreateIamPolicies, and Publish
13
+ * Versus Stigmer Cloud, OSS excludes the CreateIamPolicies and Publish
15
14
  * steps. Deliberately NO same-org rule on create, unlike WorkflowInstance:
16
15
  * an agent is a shareable blueprint, and one agent legitimately has
17
- * instances in several orgs (the marketplace case) — cloud governs
18
- * cross-org creation with FGA on the parent agent; OSS has no
19
- * authorization layer, so cross-org creation is allowed.
16
+ * instances in several orgs (the marketplace case). What governs a
17
+ * cross-org instance in both editions is the create lane's authorization
18
+ * (steps.ts, resolveInstanceCreateTargets): the caller's standing in the
19
+ * instance's organization and on the parent agent.
20
20
  */
21
21
  import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
22
22
 
@@ -47,6 +47,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
47
47
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
48
48
  import { RequestContext } from "../../pipeline/request-context.js";
49
49
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
50
+ import {
51
+ loadedTargetAsMethod,
52
+ newAuthorizeResolvedTargetStep,
53
+ } from "../../pipeline/steps/authorize-resolved-target.js";
50
54
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
51
55
  import {
52
56
  newAuthorizeVisibilityTransitionStep,
@@ -106,6 +110,7 @@ import {
106
110
  newLoadParentAgentStep,
107
111
  newRejectDefaultInstanceVisibilityUpdateStep,
108
112
  newValidateInstanceUpdateStep,
113
+ resolveInstanceCreateTargets,
109
114
  } from "./steps.js";
110
115
  import type { ParentAgentLoaderProvider } from "./steps.js";
111
116
 
@@ -181,6 +186,14 @@ async function createInstance(
181
186
  .addStep(newValidateVisibilityStep())
182
187
  .addStep(newResolveSlugStep())
183
188
  .addStep(newLoadParentAgentStep(deps.parentAgentLoader, deps.logger))
189
+ // Before the duplicate check, so a refused caller learns nothing about
190
+ // which slugs exist (steps.ts, resolveInstanceCreateTargets).
191
+ .addStep(
192
+ newAuthorizeResolvedTargetStep(
193
+ deps.authorizer,
194
+ resolveInstanceCreateTargets,
195
+ ),
196
+ )
184
197
  .addStep(newCheckDuplicateStep(deps.store))
185
198
  .addStep(newBuildNewStateStep())
186
199
  .addStep(newGuardPublicVisibilityStep(deps.authorizer))
@@ -548,6 +561,12 @@ async function getByReference(
548
561
  )
549
562
  .addStep(newValidateProtoStep())
550
563
  .addStep(newLoadByReferenceStep(deps.store, AgentInstanceSchema))
564
+ .addStep(
565
+ newAuthorizeResolvedTargetStep(
566
+ deps.authorizer,
567
+ loadedTargetAsMethod(AgentInstanceQueryController.method.get),
568
+ ),
569
+ )
551
570
  .build()
552
571
  .execute(reqCtx);
553
572
  return reqCtx.get(TARGET_RESOURCE_KEY) as AgentInstance;
@@ -16,8 +16,10 @@ import type { AgentInstance } from "@stigmer/protos/ai/stigmer/agentic/agentinst
16
16
  import { AgentInstanceListSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/io_pb";
17
17
  import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/query_pb";
18
18
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
19
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
19
20
 
20
21
  import type { Logger } from "../../boot/logger.js";
22
+ import { isServerComposedRequest } from "../../extensions/identity.js";
21
23
  import { isDefaultInstance } from "../../pipeline/apiresource-labels.js";
22
24
  import {
23
25
  failedPreconditionError,
@@ -29,6 +31,7 @@ import type { ListReadScope } from "../../extensions/list-read-scope.js";
29
31
  import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
30
32
  import type { PipelineStep } from "../../pipeline/pipeline.js";
31
33
  import type { RequestContext } from "../../pipeline/request-context.js";
34
+ import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
32
35
  import {
33
36
  compareCreatedAtDesc,
34
37
  matchesAllLabels,
@@ -54,18 +57,19 @@ export interface ParentAgentLoader {
54
57
  */
55
58
  export type ParentAgentLoaderProvider = () => ParentAgentLoader;
56
59
 
60
+ /** Context key for the loaded parent agent, read by the create check below. */
61
+ export const PARENT_AGENT_KEY = "parent_agent";
62
+
57
63
  /**
58
64
  * LoadParentAgent — create.go: an unknown spec.agent_id is rejected with
59
65
  * NotFound instead of persisting a dangling instance (oss#645), converging
60
66
  * on cloud's LoadParentAgent step and this server's own WorkflowInstance
61
- * create pipeline.
62
- *
63
- * Unlike its WorkflowInstance twin, the loaded agent is NOT stored in the
64
- * request context: nothing downstream consumes it (cloud's consumer is the
65
- * FGA authorize step, which OSS excludes; the WorkflowInstance twin's
66
- * consumer is the same-org rule, which agent instances deliberately do not
67
- * have — an agent is a shareable blueprint, and one agent legitimately has
68
- * instances in several orgs: the marketplace case).
67
+ * create pipeline. The loaded agent is stashed under PARENT_AGENT_KEY for
68
+ * the authorization step that follows; no same-org rule reads it, because
69
+ * an agent is a shareable blueprint and one agent legitimately has
70
+ * instances in several orgs (the marketplace case) — the instance's
71
+ * organization is the caller's, and the check below asks the caller's
72
+ * standing there.
69
73
  */
70
74
  export function newLoadParentAgentStep(
71
75
  agentLoader: ParentAgentLoaderProvider,
@@ -95,10 +99,80 @@ export function newLoadParentAgentStep(
95
99
  agentId,
96
100
  org: parentAgent.metadata?.org ?? "",
97
101
  });
102
+ ctx.set(PARENT_AGENT_KEY, parentAgent);
98
103
  },
99
104
  };
100
105
  }
101
106
 
107
+ /** The deny copy of the parent's bar, the Java AgentInstanceCreateHandler's. */
108
+ export const INSTANCE_PARENT_DENIED_MESSAGE =
109
+ "You don't have permission to create instances of this agent";
110
+
111
+ /** The deny copy of the organization's bar. */
112
+ export const INSTANCE_ORGANIZATION_DENIED_MESSAGE =
113
+ "unauthorized to create agent instance in this organization";
114
+
115
+ /**
116
+ * The create lane's authorization questions, for AuthorizeResolvedTarget
117
+ * after LoadParentAgent. The RPC is is_skip_authorization because its
118
+ * target is the PARENT, loaded from spec.agent_id, not a request field.
119
+ *
120
+ * A personal instance asks two questions, the organization's first so an
121
+ * outsider learns nothing about the agent from which copy came back:
122
+ * may the caller create instances in metadata.org (can_create_agent_instance,
123
+ * member-level: the instance is the caller's conversation shell in their
124
+ * own organization, which may differ from the agent's — the marketplace
125
+ * case), and may the caller instantiate this agent (can_create_instance on
126
+ * the parent, which the model resolves to whoever may run it).
127
+ *
128
+ * A DEFAULT instance asks nothing: it is the agent's row, not the caller's.
129
+ * The server composes one for the run's human when the agent has none
130
+ * (attribution stays the person's), and the run was already admitted one
131
+ * step earlier by the lane that vouched for it — a guest, a channel turn,
132
+ * a schedule fire hold no viewer tuple on the agent and would be refused
133
+ * here for a row that is not theirs. The arm is keyed on what only the
134
+ * server can produce: the request entered through the in-process transport
135
+ * (the wire cannot claim that; the propagation header is stripped at the
136
+ * chassis), it carries the reserved default-instance label (which the label
137
+ * guard lets only such a request write), and its organization is the
138
+ * parent's (the invariant buildDefaultInstanceRequest holds). A labelled
139
+ * request that fails any of the three takes the personal bar in full — a
140
+ * mis-orged default instance is judged as the human's own act, never waved
141
+ * through.
142
+ */
143
+ export function resolveInstanceCreateTargets(
144
+ ctx: RequestContext<typeof AgentInstanceSchema>,
145
+ ): ReadonlyArray<AuthorizationTarget> {
146
+ const parent = ctx.get(PARENT_AGENT_KEY) as Agent | undefined;
147
+ if (parent === undefined) {
148
+ throw new Error("parent agent not found in context");
149
+ }
150
+ const metadata = ctx.newState.metadata;
151
+ const org = metadata?.org ?? "";
152
+ const parentId = parent.metadata?.id ?? "";
153
+ if (
154
+ isDefaultInstance(metadata) &&
155
+ isServerComposedRequest(ctx.callerIdentity) &&
156
+ org === (parent.metadata?.org ?? "")
157
+ ) {
158
+ return [];
159
+ }
160
+ return [
161
+ {
162
+ permission: IamPermission.can_create_agent_instance,
163
+ resourceKind: ApiResourceKind.organization,
164
+ resourceId: org,
165
+ deniedMessage: INSTANCE_ORGANIZATION_DENIED_MESSAGE,
166
+ },
167
+ {
168
+ permission: IamPermission.can_create_instance,
169
+ resourceKind: ApiResourceKind.agent,
170
+ resourceId: parentId,
171
+ deniedMessage: INSTANCE_PARENT_DENIED_MESSAGE,
172
+ },
173
+ ];
174
+ }
175
+
102
176
  /**
103
177
  * ValidateInstanceUpdate — update.go: enforces the instance's immutable
104
178
  * identity on update — spec.agent_id must keep referencing the same agent.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pins the create lane's two-arm consent bar (steps.ts,
3
+ * resolveShareCreateTargets), the Java handler's: a same-org share asks
4
+ * can_edit on the referenced agent; a cross-org share asks can_execute on
5
+ * the agent and then can_create_agent_share on the SHARING organization,
6
+ * agent first; each question carries its byte-pinned copy; a missing
7
+ * referenced agent throws.
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { create } from "@bufbuild/protobuf";
11
+
12
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
13
+ import { AgentShareSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
14
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
15
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
16
+
17
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
18
+ import { RequestContext } from "../../../pipeline/request-context.js";
19
+ import {
20
+ REFERENCED_AGENT_KEY,
21
+ SHARE_AGENT_DENIED_MESSAGE,
22
+ SHARE_ORGANIZATION_DENIED_MESSAGE,
23
+ resolveShareCreateTargets,
24
+ } from "../steps.js";
25
+
26
+ const agent = create(AgentSchema, {
27
+ metadata: { id: "agt_01public", name: "helper", org: "publisher-org" },
28
+ });
29
+
30
+ function ctxFor(shareOrg: string, agentOrg: string, stash = true) {
31
+ const ctx = new RequestContext(
32
+ AgentShareSchema,
33
+ create(AgentShareSchema, {
34
+ metadata: { name: "helper", org: shareOrg },
35
+ spec: { agentRef: { org: agentOrg, slug: "helper" } },
36
+ }),
37
+ testCallerIdentity(),
38
+ ApiResourceKind.agent_share,
39
+ );
40
+ if (stash) {
41
+ ctx.set(REFERENCED_AGENT_KEY, agent);
42
+ }
43
+ return ctx;
44
+ }
45
+
46
+ describe("resolveShareCreateTargets", () => {
47
+ it("a same-org share asks can_edit on the referenced agent", () => {
48
+ expect(
49
+ resolveShareCreateTargets(ctxFor("publisher-org", "publisher-org")),
50
+ ).toEqual([
51
+ {
52
+ permission: IamPermission.can_edit,
53
+ resourceKind: ApiResourceKind.agent,
54
+ resourceId: "agt_01public",
55
+ deniedMessage: SHARE_AGENT_DENIED_MESSAGE,
56
+ },
57
+ ]);
58
+ });
59
+
60
+ it("a cross-org share asks can_execute on the agent, then can_create_agent_share on the sharing organization", () => {
61
+ expect(
62
+ resolveShareCreateTargets(ctxFor("consumer-org", "publisher-org")),
63
+ ).toEqual([
64
+ {
65
+ permission: IamPermission.can_execute,
66
+ resourceKind: ApiResourceKind.agent,
67
+ resourceId: "agt_01public",
68
+ deniedMessage: SHARE_AGENT_DENIED_MESSAGE,
69
+ },
70
+ {
71
+ permission: IamPermission.can_create_agent_share,
72
+ resourceKind: ApiResourceKind.organization,
73
+ resourceId: "consumer-org",
74
+ deniedMessage: SHARE_ORGANIZATION_DENIED_MESSAGE,
75
+ },
76
+ ]);
77
+ });
78
+
79
+ it("a missing referenced agent is a broken chain invariant and throws", () => {
80
+ expect(() =>
81
+ resolveShareCreateTargets(
82
+ ctxFor("publisher-org", "publisher-org", false),
83
+ ),
84
+ ).toThrow("referenced agent not found in context");
85
+ });
86
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Pins AuthorizeMemberAudience (steps.ts), the member-profile lane's gate:
3
+ * membership (can_view on the reference's organization) is asked before
4
+ * the share is loaded; deny and not-found both answer the lane's one
5
+ * NOT_FOUND, byte-identical to a missing share, so a share URL teaches a
6
+ * non-member nothing; unavailable is INTERNAL; an empty org asks nothing
7
+ * and is left to the loader's INVALID_ARGUMENT; the internal class skips.
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { create } from "@bufbuild/protobuf";
11
+ import { Code, ConnectError } from "@connectrpc/connect";
12
+
13
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
14
+ import { ApiResourceReferenceSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
15
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
16
+
17
+ import type {
18
+ Authorizer,
19
+ AuthzCheck,
20
+ AuthzDecision,
21
+ } from "../../../extensions/authorizer.js";
22
+ import type { CallerIdentity } from "../../../extensions/identity.js";
23
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
24
+ import { RequestContext } from "../../../pipeline/request-context.js";
25
+ import { AUTHORIZATION_UNAVAILABLE_MESSAGE } from "../../../pipeline/steps/authorize.js";
26
+ import { newAuthorizeMemberAudienceStep, sharedNotFound } from "../steps.js";
27
+
28
+ function fakeAuthorizer(decision: AuthzDecision): {
29
+ authorizer: Authorizer;
30
+ checks: AuthzCheck[];
31
+ } {
32
+ const checks: AuthzCheck[] = [];
33
+ return {
34
+ checks,
35
+ authorizer: {
36
+ authorize(_caller, check) {
37
+ checks.push(check);
38
+ return Promise.resolve(decision);
39
+ },
40
+ },
41
+ };
42
+ }
43
+
44
+ function ctxFor(org: string, caller: CallerIdentity = testCallerIdentity()) {
45
+ return new RequestContext(
46
+ ApiResourceReferenceSchema,
47
+ create(ApiResourceReferenceSchema, { org, slug: "helper" }),
48
+ caller,
49
+ ApiResourceKind.agent_share,
50
+ );
51
+ }
52
+
53
+ async function captureError(
54
+ run: () => void | Promise<void>,
55
+ ): Promise<ConnectError> {
56
+ try {
57
+ await run();
58
+ } catch (error) {
59
+ return ConnectError.from(error);
60
+ }
61
+ throw new Error("expected the step to reject");
62
+ }
63
+
64
+ describe("AuthorizeMemberAudience", () => {
65
+ it("asks can_view on the reference's organization and admits an allow", async () => {
66
+ const { authorizer, checks } = fakeAuthorizer({ kind: "allow" });
67
+ await newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme"));
68
+ expect(checks).toEqual([
69
+ {
70
+ permission: IamPermission.can_view,
71
+ resourceKind: ApiResourceKind.organization,
72
+ resourceId: "acme",
73
+ },
74
+ ]);
75
+ });
76
+
77
+ it("deny and not-found both answer the lane's NOT_FOUND, byte-identical to a missing share", async () => {
78
+ for (const decision of [
79
+ { kind: "deny", reason: "not a member" },
80
+ { kind: "not-found" },
81
+ ] as const) {
82
+ const { authorizer } = fakeAuthorizer(decision);
83
+ const error = await captureError(() =>
84
+ newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme")),
85
+ );
86
+ expect(error.code).toBe(Code.NotFound);
87
+ expect(error.rawMessage).toBe(sharedNotFound("helper").rawMessage);
88
+ }
89
+ });
90
+
91
+ it("unavailable is INTERNAL, never a refusal", async () => {
92
+ const { authorizer } = fakeAuthorizer({
93
+ kind: "unavailable",
94
+ cause: new Error("backend down"),
95
+ });
96
+ const error = await captureError(() =>
97
+ newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme")),
98
+ );
99
+ expect(error.code).toBe(Code.Internal);
100
+ expect(error.rawMessage).toBe(AUTHORIZATION_UNAVAILABLE_MESSAGE);
101
+ });
102
+
103
+ it("an empty org asks nothing (the loader's INVALID_ARGUMENT answers), and the internal class skips", async () => {
104
+ const denying = fakeAuthorizer({ kind: "deny", reason: "" });
105
+ await newAuthorizeMemberAudienceStep(denying.authorizer).execute(
106
+ ctxFor(""),
107
+ );
108
+ await newAuthorizeMemberAudienceStep(denying.authorizer).execute(
109
+ ctxFor("acme", testCallerIdentity({ callerClass: "internal" })),
110
+ );
111
+ expect(denying.checks).toEqual([]);
112
+ });
113
+ });
@@ -60,6 +60,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
60
60
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
61
61
  import { RequestContext } from "../../pipeline/request-context.js";
62
62
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
63
+ import {
64
+ loadedTargetAsMethod,
65
+ newAuthorizeResolvedTargetStep,
66
+ } from "../../pipeline/steps/authorize-resolved-target.js";
63
67
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
64
68
  import {
65
69
  newBuildNewStateStep,
@@ -109,6 +113,8 @@ import {
109
113
  newValidateShareUpdateStep,
110
114
  sharedNotFound,
111
115
  sharingLinkTokenAllowed,
116
+ resolveShareCreateTargets,
117
+ newAuthorizeMemberAudienceStep,
112
118
  } from "./steps.js";
113
119
 
114
120
  export interface AgentShareControllerDeps {
@@ -176,6 +182,13 @@ async function createShare(
176
182
  .addStep(newValidateProtoStep())
177
183
  .addStep(newValidateVisibilityStep())
178
184
  .addStep(newResolveShareDefaultsStep(deps.store))
185
+ // Before the duplicate check (steps.ts, resolveShareCreateTargets).
186
+ .addStep(
187
+ newAuthorizeResolvedTargetStep(
188
+ deps.authorizer,
189
+ resolveShareCreateTargets,
190
+ ),
191
+ )
179
192
  .addStep(newResolveSlugStep())
180
193
  .addStep(newCheckDuplicateStep(deps.store))
181
194
  .addStep(newBuildNewStateStep())
@@ -491,6 +504,12 @@ async function getByReference(
491
504
  )
492
505
  .addStep(newValidateProtoStep())
493
506
  .addStep(newLoadByReferenceStep(deps.store, AgentShareSchema))
507
+ .addStep(
508
+ newAuthorizeResolvedTargetStep(
509
+ deps.authorizer,
510
+ loadedTargetAsMethod(AgentShareQueryController.method.get),
511
+ ),
512
+ )
494
513
  .build()
495
514
  .execute(reqCtx);
496
515
  return reqCtx.get(TARGET_RESOURCE_KEY) as AgentShare;
@@ -797,6 +816,8 @@ async function getSharedProfileForMember(
797
816
  ),
798
817
  )
799
818
  .addStep(newValidateProtoStep())
819
+ // Membership before existence (steps.ts, AuthorizeMemberAudience).
820
+ .addStep(newAuthorizeMemberAudienceStep(deps.authorizer))
800
821
  .addStep(newLoadShareForMemberProfileStep(deps.store))
801
822
  .addStep(newProjectMemberSharedProfileStep(deps.store))
802
823
  .build()