@stigmer/server 3.21.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 (185) 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/domain/agent/controller.d.ts.map +1 -1
  11. package/dist/domain/agent/controller.js +2 -0
  12. package/dist/domain/agent/controller.js.map +1 -1
  13. package/dist/domain/agentchannel/controller.d.ts.map +1 -1
  14. package/dist/domain/agentchannel/controller.js +6 -1
  15. package/dist/domain/agentchannel/controller.js.map +1 -1
  16. package/dist/domain/agentchannel/steps.d.ts +19 -0
  17. package/dist/domain/agentchannel/steps.d.ts.map +1 -1
  18. package/dist/domain/agentchannel/steps.js +32 -0
  19. package/dist/domain/agentchannel/steps.js.map +1 -1
  20. package/dist/domain/agentinstance/controller.d.ts +5 -5
  21. package/dist/domain/agentinstance/controller.d.ts.map +1 -1
  22. package/dist/domain/agentinstance/controller.js +6 -1
  23. package/dist/domain/agentinstance/controller.js.map +1 -1
  24. package/dist/domain/agentinstance/steps.d.ts +43 -8
  25. package/dist/domain/agentinstance/steps.d.ts.map +1 -1
  26. package/dist/domain/agentinstance/steps.js +71 -8
  27. package/dist/domain/agentinstance/steps.js.map +1 -1
  28. package/dist/domain/agentshare/controller.d.ts.map +1 -1
  29. package/dist/domain/agentshare/controller.js +7 -1
  30. package/dist/domain/agentshare/controller.js.map +1 -1
  31. package/dist/domain/agentshare/steps.d.ts +58 -0
  32. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  33. package/dist/domain/agentshare/steps.js +109 -4
  34. package/dist/domain/agentshare/steps.js.map +1 -1
  35. package/dist/domain/artifact/controller.js +21 -25
  36. package/dist/domain/artifact/controller.js.map +1 -1
  37. package/dist/domain/channelapp/controller.d.ts.map +1 -1
  38. package/dist/domain/channelapp/controller.js +2 -0
  39. package/dist/domain/channelapp/controller.js.map +1 -1
  40. package/dist/domain/environment/controller.d.ts.map +1 -1
  41. package/dist/domain/environment/controller.js +2 -0
  42. package/dist/domain/environment/controller.js.map +1 -1
  43. package/dist/domain/executioncontext/controller.d.ts.map +1 -1
  44. package/dist/domain/executioncontext/controller.js +2 -0
  45. package/dist/domain/executioncontext/controller.js.map +1 -1
  46. package/dist/domain/executioncontext/steps.d.ts.map +1 -1
  47. package/dist/domain/executioncontext/steps.js +2 -1
  48. package/dist/domain/executioncontext/steps.js.map +1 -1
  49. package/dist/domain/mcpserver/connect.d.ts.map +1 -1
  50. package/dist/domain/mcpserver/connect.js +8 -7
  51. package/dist/domain/mcpserver/connect.js.map +1 -1
  52. package/dist/domain/mcpserver/controller.d.ts.map +1 -1
  53. package/dist/domain/mcpserver/controller.js +2 -0
  54. package/dist/domain/mcpserver/controller.js.map +1 -1
  55. package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
  56. package/dist/domain/mcpserver/start-connect.js +8 -7
  57. package/dist/domain/mcpserver/start-connect.js.map +1 -1
  58. package/dist/domain/memory/controller.d.ts.map +1 -1
  59. package/dist/domain/memory/controller.js +6 -1
  60. package/dist/domain/memory/controller.js.map +1 -1
  61. package/dist/domain/memory/steps.d.ts +23 -0
  62. package/dist/domain/memory/steps.d.ts.map +1 -1
  63. package/dist/domain/memory/steps.js +35 -0
  64. package/dist/domain/memory/steps.js.map +1 -1
  65. package/dist/domain/oauthapp/controller.d.ts.map +1 -1
  66. package/dist/domain/oauthapp/controller.js +2 -0
  67. package/dist/domain/oauthapp/controller.js.map +1 -1
  68. package/dist/domain/plugin/controller.d.ts.map +1 -1
  69. package/dist/domain/plugin/controller.js +5 -24
  70. package/dist/domain/plugin/controller.js.map +1 -1
  71. package/dist/domain/schedule/controller.d.ts.map +1 -1
  72. package/dist/domain/schedule/controller.js +7 -1
  73. package/dist/domain/schedule/controller.js.map +1 -1
  74. package/dist/domain/schedule/steps.d.ts +19 -0
  75. package/dist/domain/schedule/steps.d.ts.map +1 -1
  76. package/dist/domain/schedule/steps.js +32 -0
  77. package/dist/domain/schedule/steps.js.map +1 -1
  78. package/dist/domain/session/steps.d.ts +8 -9
  79. package/dist/domain/session/steps.d.ts.map +1 -1
  80. package/dist/domain/session/steps.js +17 -21
  81. package/dist/domain/session/steps.js.map +1 -1
  82. package/dist/domain/skill/controller.d.ts.map +1 -1
  83. package/dist/domain/skill/controller.js +8 -17
  84. package/dist/domain/skill/controller.js.map +1 -1
  85. package/dist/domain/workflow/controller.d.ts.map +1 -1
  86. package/dist/domain/workflow/controller.js +30 -107
  87. package/dist/domain/workflow/controller.js.map +1 -1
  88. package/dist/domain/workflow/version-resolution.d.ts +5 -0
  89. package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
  90. package/dist/domain/workflow/version-resolution.js +113 -0
  91. package/dist/domain/workflow/version-resolution.js.map +1 -0
  92. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  93. package/dist/domain/workflowinstance/controller.js +6 -1
  94. package/dist/domain/workflowinstance/controller.js.map +1 -1
  95. package/dist/domain/workflowinstance/steps.d.ts +20 -0
  96. package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
  97. package/dist/domain/workflowinstance/steps.js +39 -0
  98. package/dist/domain/workflowinstance/steps.js.map +1 -1
  99. package/dist/extensions/identity.d.ts +19 -0
  100. package/dist/extensions/identity.d.ts.map +1 -1
  101. package/dist/extensions/identity.js +22 -3
  102. package/dist/extensions/identity.js.map +1 -1
  103. package/dist/extensions/list-read-scope.d.ts +41 -7
  104. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  105. package/dist/extensions/list-read-scope.js +13 -2
  106. package/dist/extensions/list-read-scope.js.map +1 -1
  107. package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
  108. package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
  109. package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
  110. package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
  111. package/dist/pipeline/steps/authorize.d.ts +27 -0
  112. package/dist/pipeline/steps/authorize.d.ts.map +1 -1
  113. package/dist/pipeline/steps/authorize.js +49 -10
  114. package/dist/pipeline/steps/authorize.js.map +1 -1
  115. package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
  116. package/dist/pipeline/steps/guard-memory-capture.js +2 -1
  117. package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
  118. package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
  119. package/dist/pipeline/steps/guard-reserved-labels.js +6 -7
  120. package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
  121. package/dist/pipeline/steps/helpers.d.ts +9 -5
  122. package/dist/pipeline/steps/helpers.d.ts.map +1 -1
  123. package/dist/pipeline/steps/helpers.js +11 -15
  124. package/dist/pipeline/steps/helpers.js.map +1 -1
  125. package/dist/pipeline/steps/version-history.d.ts +11 -0
  126. package/dist/pipeline/steps/version-history.d.ts.map +1 -1
  127. package/dist/pipeline/steps/version-history.js +26 -0
  128. package/dist/pipeline/steps/version-history.js.map +1 -1
  129. package/package.json +6 -6
  130. package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
  131. package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
  132. package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
  133. package/src/authorization/evaluator.ts +7 -0
  134. package/src/authorization/list-read-scope.ts +22 -11
  135. package/src/authorization/model/__tests__/registry.test.ts +2 -0
  136. package/src/authorization/model/organization.ts +2 -0
  137. package/src/domain/agent/controller.ts +10 -0
  138. package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
  139. package/src/domain/agentchannel/controller.ts +19 -0
  140. package/src/domain/agentchannel/steps.ts +40 -0
  141. package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
  142. package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
  143. package/src/domain/agentinstance/controller.ts +24 -5
  144. package/src/domain/agentinstance/steps.ts +82 -8
  145. package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
  146. package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
  147. package/src/domain/agentshare/controller.ts +21 -0
  148. package/src/domain/agentshare/steps.ts +181 -15
  149. package/src/domain/artifact/controller.ts +18 -22
  150. package/src/domain/channelapp/controller.ts +10 -0
  151. package/src/domain/environment/controller.ts +10 -0
  152. package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
  153. package/src/domain/executioncontext/controller.ts +10 -0
  154. package/src/domain/executioncontext/steps.ts +2 -1
  155. package/src/domain/mcpserver/connect.ts +11 -10
  156. package/src/domain/mcpserver/controller.ts +13 -1
  157. package/src/domain/mcpserver/start-connect.ts +17 -11
  158. package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
  159. package/src/domain/memory/controller.ts +11 -0
  160. package/src/domain/memory/steps.ts +41 -0
  161. package/src/domain/oauthapp/controller.ts +10 -0
  162. package/src/domain/plugin/controller.ts +22 -36
  163. package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
  164. package/src/domain/schedule/controller.ts +20 -0
  165. package/src/domain/schedule/steps.ts +45 -2
  166. package/src/domain/session/steps.ts +24 -32
  167. package/src/domain/skill/controller.ts +24 -23
  168. package/src/domain/workflow/controller.ts +58 -145
  169. package/src/domain/workflow/version-resolution.ts +166 -0
  170. package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
  171. package/src/domain/workflowinstance/controller.ts +19 -0
  172. package/src/domain/workflowinstance/steps.ts +49 -1
  173. package/src/extensions/__tests__/composed-support.ts +38 -4
  174. package/src/extensions/__tests__/identity.test.ts +44 -1
  175. package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
  176. package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
  177. package/src/extensions/identity.ts +23 -5
  178. package/src/extensions/list-read-scope.ts +47 -7
  179. package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
  180. package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
  181. package/src/pipeline/steps/authorize.ts +70 -13
  182. package/src/pipeline/steps/guard-memory-capture.ts +2 -1
  183. package/src/pipeline/steps/guard-reserved-labels.ts +6 -9
  184. package/src/pipeline/steps/helpers.ts +11 -15
  185. package/src/pipeline/steps/version-history.ts +34 -0
@@ -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;
@@ -51,7 +51,6 @@ import type {
51
51
  ApiResourceReference,
52
52
  UpdateVisibilityInput,
53
53
  } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
54
- import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
55
54
 
56
55
  import type { ContentAddressedArchiveStore } from "../../archive/content-store.js";
57
56
  import { MAX_ZIP_SIZE } from "../../archive/limits.js";
@@ -74,10 +73,7 @@ import {
74
73
  newRecordVisibilityBeforeUpdateStep,
75
74
  newUpdateVisibilityTuplesStep,
76
75
  } from "../../pipeline/steps/authorization-tuples.js";
77
- import {
78
- authorizeResolvedResource,
79
- newAuthorizeStep,
80
- } from "../../pipeline/steps/authorize.js";
76
+ import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
81
77
  import { setAuditFieldsForUpdate } from "../../pipeline/steps/defaults.js";
82
78
  import {
83
79
  newDeleteResourceStep,
@@ -96,12 +92,16 @@ import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
96
92
  import { newValidateVisibilityUpdateStep } from "../../pipeline/steps/validate-visibility.js";
97
93
  import { newDeleteVersionArchivesStep } from "../../pipeline/steps/version-archive.js";
98
94
  import {
99
- LIST_VERSIONS_RESOURCE_ID_KEY,
100
95
  LIST_VERSIONS_RESPONSE_KEY,
101
96
  newLoadAndMapVersionsStep,
102
97
  newLoadByReferenceWithVersionStep,
103
98
  newResolveBySlugForVersionsStep,
99
+ versionHistoryTarget,
104
100
  } from "../../pipeline/steps/version-history.js";
101
+ import {
102
+ loadedTargetAsMethod,
103
+ newAuthorizeResolvedTargetStep,
104
+ } from "../../pipeline/steps/authorize-resolved-target.js";
105
105
  import type { VersionHistoryBinding } from "../../pipeline/steps/version-history.js";
106
106
  import { newAuthorizeVisibilityTransitionStep } from "../../pipeline/steps/visibility-gates.js";
107
107
  import { metadataOf } from "../../pipeline/steps/shapes.js";
@@ -718,22 +718,13 @@ async function getByReference(
718
718
  "LoadPluginByReference",
719
719
  ),
720
720
  )
721
- .addStep({
722
- name: "AuthorizeResolvedPlugin",
723
- async execute(stepCtx): Promise<void> {
724
- const resolved = stepCtx.get(TARGET_RESOURCE_KEY) as Plugin;
725
- await authorizeResolvedResource(
726
- deps.authorizer,
727
- stepCtx.callerIdentity,
728
- {
729
- permission: IamPermission.can_view,
730
- resourceKind: ApiResourceKind.plugin,
731
- resourceId: resolved.metadata?.id ?? "",
732
- },
733
- "unauthorized to get plugin",
734
- );
735
- },
736
- })
721
+ .addStep(
722
+ newAuthorizeResolvedTargetStep(
723
+ deps.authorizer,
724
+ loadedTargetAsMethod(PluginQueryController.method.get),
725
+ "AuthorizeResolvedPlugin",
726
+ ),
727
+ )
737
728
  .build()
738
729
  .execute(reqCtx);
739
730
  return reqCtx.get(TARGET_RESOURCE_KEY) as Plugin;
@@ -817,21 +808,16 @@ async function listVersions(
817
808
  "ResolvePluginBySlug",
818
809
  ),
819
810
  )
820
- .addStep({
821
- name: "AuthorizeResolvedPlugin",
822
- async execute(stepCtx): Promise<void> {
823
- await authorizeResolvedResource(
824
- deps.authorizer,
825
- stepCtx.callerIdentity,
826
- {
827
- permission: IamPermission.can_view,
828
- resourceKind: ApiResourceKind.plugin,
829
- resourceId: stepCtx.get(LIST_VERSIONS_RESOURCE_ID_KEY) as string,
830
- },
811
+ .addStep(
812
+ newAuthorizeResolvedTargetStep(
813
+ deps.authorizer,
814
+ versionHistoryTarget(
815
+ ApiResourceKind.plugin,
831
816
  "unauthorized to view plugin version history",
832
- );
833
- },
834
- })
817
+ ),
818
+ "AuthorizeResolvedPlugin",
819
+ ),
820
+ )
835
821
  .addStep(
836
822
  newLoadAndMapVersionsStep(
837
823
  deps.store,
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Pins the create lane's authorization question (steps.ts,
3
+ * resolveScheduleCreateTargets): can_edit on the REFERENCED AGENT with the
4
+ * Java handler's copy, and nothing on the organization (the same-org
5
+ * invariant already binds the schedule to the agent's); a missing
6
+ * referenced agent throws.
7
+ */
8
+ import { describe, expect, it } from "vitest";
9
+ import { create } from "@bufbuild/protobuf";
10
+
11
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
12
+ import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/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 { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
17
+ import { RequestContext } from "../../../pipeline/request-context.js";
18
+ import {
19
+ REFERENCED_AGENT_KEY,
20
+ SCHEDULE_CREATE_DENIED_MESSAGE,
21
+ resolveScheduleCreateTargets,
22
+ } from "../steps.js";
23
+
24
+ const agent = create(AgentSchema, {
25
+ metadata: { id: "agt_01target", name: "helper", org: "acme" },
26
+ });
27
+
28
+ function ctxFor(stash = true) {
29
+ const ctx = new RequestContext(
30
+ ScheduleSchema,
31
+ create(ScheduleSchema, { metadata: { name: "nightly", org: "acme" } }),
32
+ testCallerIdentity(),
33
+ ApiResourceKind.schedule,
34
+ );
35
+ if (stash) {
36
+ ctx.set(REFERENCED_AGENT_KEY, agent);
37
+ }
38
+ return ctx;
39
+ }
40
+
41
+ describe("resolveScheduleCreateTargets", () => {
42
+ it("asks can_edit on the referenced agent and nothing else", () => {
43
+ expect(resolveScheduleCreateTargets(ctxFor())).toEqual([
44
+ {
45
+ permission: IamPermission.can_edit,
46
+ resourceKind: ApiResourceKind.agent,
47
+ resourceId: "agt_01target",
48
+ deniedMessage: SCHEDULE_CREATE_DENIED_MESSAGE,
49
+ },
50
+ ]);
51
+ });
52
+
53
+ it("a missing referenced agent is a broken chain invariant and throws", () => {
54
+ expect(() => resolveScheduleCreateTargets(ctxFor(false))).toThrow(
55
+ "referenced agent not found in context",
56
+ );
57
+ });
58
+ });
@@ -62,6 +62,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
62
62
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
63
63
  import { RequestContext } from "../../pipeline/request-context.js";
64
64
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
65
+ import {
66
+ loadedTargetAsMethod,
67
+ newAuthorizeResolvedTargetStep,
68
+ } from "../../pipeline/steps/authorize-resolved-target.js";
65
69
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
66
70
  import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
67
71
  import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
@@ -116,6 +120,7 @@ import { newClearSchedulePauseStep } from "./resume.js";
116
120
  import {
117
121
  newResolveScheduleDefaultsStep,
118
122
  newValidateScheduleUpdateStep,
123
+ resolveScheduleCreateTargets,
119
124
  } from "./steps.js";
120
125
  import {
121
126
  TRIGGER_RESULT_KEY,
@@ -201,6 +206,15 @@ async function createSchedule(
201
206
  .addStep(newValidateProtoStep())
202
207
  .addStep(newValidateVisibilityStep())
203
208
  .addStep(newResolveScheduleDefaultsStep(deps))
209
+ // Before the duplicate check and before ArmSchedule: a refused caller
210
+ // learns nothing about existing slugs and costs no Temporal schedule
211
+ // (steps.ts, resolveScheduleCreateTargets).
212
+ .addStep(
213
+ newAuthorizeResolvedTargetStep(
214
+ deps.authorizer,
215
+ resolveScheduleCreateTargets,
216
+ ),
217
+ )
204
218
  .addStep(newResolveSlugStep())
205
219
  .addStep(newCheckDuplicateStep(deps.store))
206
220
  .addStep(newBuildNewStateStep())
@@ -477,6 +491,12 @@ async function getByReference(
477
491
  )
478
492
  .addStep(newValidateProtoStep())
479
493
  .addStep(newLoadByReferenceStep(deps.store, ScheduleSchema))
494
+ .addStep(
495
+ newAuthorizeResolvedTargetStep(
496
+ deps.authorizer,
497
+ loadedTargetAsMethod(ScheduleQueryController.method.get),
498
+ ),
499
+ )
480
500
  .build()
481
501
  .execute(reqCtx);
482
502
  return reqCtx.get(TARGET_RESOURCE_KEY) as Schedule;
@@ -8,10 +8,12 @@
8
8
  * __tests__/schedule.composed.test.ts.
9
9
  */
10
10
  import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
11
+ import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
11
12
  import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
12
13
  import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
13
14
  import type { ScheduleSpec } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/spec_pb";
14
15
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
16
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
15
17
 
16
18
  import {
17
19
  failedPreconditionError,
@@ -21,6 +23,7 @@ import {
21
23
  } from "../../pipeline/errors.js";
22
24
  import type { PipelineStep } from "../../pipeline/pipeline.js";
23
25
  import type { RequestContext } from "../../pipeline/request-context.js";
26
+ import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
24
27
  import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
25
28
  import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
26
29
  import type { Store } from "../../store/interface.js";
@@ -128,10 +131,47 @@ export function newResolveScheduleDefaultsStep(
128
131
  if (agentRef !== undefined) {
129
132
  agentRef.org = refOrg;
130
133
  }
134
+ ctx.set(REFERENCED_AGENT_KEY, found);
131
135
  },
132
136
  };
133
137
  }
134
138
 
139
+ /**
140
+ * Context key for the agent ResolveScheduleDefaults resolved from
141
+ * spec.agent.agent_ref, read by the create lane's authorization step.
142
+ */
143
+ export const REFERENCED_AGENT_KEY = "scheduleReferencedAgent";
144
+
145
+ /** The deny copy of the create bar, the Java ScheduleCreateHandler's. */
146
+ export const SCHEDULE_CREATE_DENIED_MESSAGE =
147
+ "You don't have permission to schedule this agent";
148
+
149
+ /**
150
+ * The create lane's authorization question, for AuthorizeResolvedTarget
151
+ * after ResolveScheduleDefaults: can_edit on the REFERENCED AGENT. The RPC
152
+ * is is_skip_authorization because its target is that agent, resolved from
153
+ * a slug, not a request field; and there is deliberately no organization
154
+ * bar — a schedule is unattended spend on an agent, an editor's act, and
155
+ * the same-org invariant the resolve step enforces already binds the
156
+ * schedule's organization to the agent's (command.proto).
157
+ */
158
+ export function resolveScheduleCreateTargets(
159
+ ctx: RequestContext<typeof ScheduleSchema>,
160
+ ): ReadonlyArray<AuthorizationTarget> {
161
+ const agent = ctx.get(REFERENCED_AGENT_KEY) as Agent | undefined;
162
+ if (agent === undefined) {
163
+ throw new Error("referenced agent not found in context");
164
+ }
165
+ return [
166
+ {
167
+ permission: IamPermission.can_edit,
168
+ resourceKind: ApiResourceKind.agent,
169
+ resourceId: agent.metadata?.id ?? "",
170
+ deniedMessage: SCHEDULE_CREATE_DENIED_MESSAGE,
171
+ },
172
+ ];
173
+ }
174
+
135
175
  /**
136
176
  * Enforces the schedule-specific workspace constraint on the shared
137
177
  * AgentInvocation (DD-018 D-3): every workspace entry must be a git_repo
@@ -140,7 +180,9 @@ export function newResolveScheduleDefaultsStep(
140
180
  * deterministic provisioning failure at 3 AM. Copy is cross-edition
141
181
  * contract (Go validateScheduleWorkspace).
142
182
  */
143
- export function validateScheduleWorkspace(spec: ScheduleSpec | undefined): void {
183
+ export function validateScheduleWorkspace(
184
+ spec: ScheduleSpec | undefined,
185
+ ): void {
144
186
  const entries =
145
187
  spec?.target.case === "agent" ? spec.target.value.workspaceEntries : [];
146
188
  for (const [i, entry] of entries.entries()) {
@@ -174,7 +216,8 @@ export function validateScheduleModelPinning(
174
216
  if (presence !== "") {
175
217
  throw invalidArgumentError(presence);
176
218
  }
177
- const invocation = spec?.target.case === "agent" ? spec.target.value : undefined;
219
+ const invocation =
220
+ spec?.target.case === "agent" ? spec.target.value : undefined;
178
221
  const existence = unknownModelPinRefusal(
179
222
  modelRegistry,
180
223
  "spec.agent.run_config.model_name",
@@ -44,7 +44,7 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
44
44
  import type { Authorizer } from "../../extensions/authorizer.js";
45
45
  import type { ListReadScope } from "../../extensions/list-read-scope.js";
46
46
  import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
47
- import { authorizeResolvedResource } from "../../pipeline/steps/authorize.js";
47
+ import { newAuthorizeResolvedTargetStep } from "../../pipeline/steps/authorize-resolved-target.js";
48
48
  import type { RequestContext } from "../../pipeline/request-context.js";
49
49
  import { RESOURCE_ID_KEY } from "../../pipeline/steps/delete.js";
50
50
  import { compareCreatedAtDesc } from "../../pipeline/steps/helpers.js";
@@ -533,41 +533,33 @@ export function newFilterByAgentInstanceStep(
533
533
 
534
534
  /**
535
535
  * AuthorizeChannelAccess — the Java SessionListByChannelHandler's
536
- * two-stage authorization, stage one (census lane 3's extra arm,
537
- * 20260830.01 ruling Q8): an explicit can_view check on the TARGET
538
- * agent_channel before any session work, so a caller without channel
539
- * access learns nothing about the channel's sessions. The mid-chain
540
- * resolved-id evaluation (the is_skip_authorization annotation makes the
541
- * position-1 step a no-op on this lane); deny copy is the Java handler's
542
- * byte-pinned "unauthorized to list channel conversations". The blank-id
543
- * refusal stays FilterByChannel's (the step behind this gate) — the gate
544
- * checks only a named channel.
536
+ * two-stage authorization, stage one: an explicit can_view check on the
537
+ * TARGET agent_channel before any session work, so a caller without
538
+ * channel access learns nothing about the channel's sessions. The
539
+ * mid-chain resolved-target evaluation (the is_skip_authorization
540
+ * annotation makes the position-1 step a no-op on this lane); deny copy is
541
+ * the Java handler's byte-pinned "unauthorized to list channel
542
+ * conversations". The blank-id refusal stays FilterByChannel's (the step
543
+ * behind this gate) — the gate asks nothing when no channel is named.
545
544
  */
546
545
  export function newAuthorizeChannelAccessStep(
547
546
  authorizer: Authorizer,
548
547
  ): PipelineStep<typeof SessionQueryController.method.listByChannel.input> {
549
- return {
550
- name: "AuthorizeChannelAccess",
551
- async execute(
552
- ctx: RequestContext<
553
- typeof SessionQueryController.method.listByChannel.input
554
- >,
555
- ): Promise<void> {
556
- if (ctx.input.channelId === "") {
557
- return;
558
- }
559
- await authorizeResolvedResource(
560
- authorizer,
561
- ctx.callerIdentity,
562
- {
563
- permission: IamPermission.can_view,
564
- resourceKind: ApiResourceKind.agent_channel,
565
- resourceId: ctx.input.channelId,
566
- },
567
- "unauthorized to list channel conversations",
568
- );
569
- },
570
- };
548
+ return newAuthorizeResolvedTargetStep(
549
+ authorizer,
550
+ ({ input }) =>
551
+ input.channelId === ""
552
+ ? []
553
+ : [
554
+ {
555
+ permission: IamPermission.can_view,
556
+ resourceKind: ApiResourceKind.agent_channel,
557
+ resourceId: input.channelId,
558
+ deniedMessage: "unauthorized to list channel conversations",
559
+ },
560
+ ],
561
+ "AuthorizeChannelAccess",
562
+ );
571
563
  }
572
564
 
573
565
  /**