@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
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import path from "node:path";
25
25
 
26
- import type { DescService } from "@bufbuild/protobuf";
26
+ import type { DescMethod, DescService } from "@bufbuild/protobuf";
27
27
  import type {
28
28
  ConnectRouter,
29
29
  Interceptor,
@@ -94,11 +94,30 @@ export function bearer(token: string): Interceptor {
94
94
  export function servedServices(
95
95
  routes: (router: ConnectRouter) => void,
96
96
  ): DescService[] {
97
- const served: DescService[] = [];
97
+ return servedRegistrations(routes).map((r) => r.service);
98
+ }
99
+
100
+ /** One `router.service(desc, implementation)` registration, as replayed. */
101
+ export interface ServedRegistration {
102
+ readonly service: DescService;
103
+ readonly implementation: Readonly<Record<string, unknown>>;
104
+ }
105
+
106
+ /**
107
+ * The registrations themselves, with each service's implementation map,
108
+ * for a reader that must know which of a service's methods are actually
109
+ * served: a partially implemented service (a method registered only when
110
+ * a composed capability carries it) lists the method on its descriptor
111
+ * and omits it from the map, and ConnectRPC answers UNIMPLEMENTED for it.
112
+ */
113
+ export function servedRegistrations(
114
+ routes: (router: ConnectRouter) => void,
115
+ ): ServedRegistration[] {
116
+ const served: ServedRegistration[] = [];
98
117
  const recorder = {
99
118
  handlers: [],
100
- service(desc: DescService) {
101
- served.push(desc);
119
+ service(desc: DescService, implementation: Record<string, unknown>) {
120
+ served.push({ service: desc, implementation });
102
121
  return recorder;
103
122
  },
104
123
  rpc() {
@@ -109,6 +128,21 @@ export function servedServices(
109
128
  return served;
110
129
  }
111
130
 
131
+ /** The methods a composed server actually serves: on a registered service AND in its implementation map. */
132
+ export function servedMethods(
133
+ routes: (router: ConnectRouter) => void,
134
+ ): DescMethod[] {
135
+ const methods: DescMethod[] = [];
136
+ for (const { service, implementation } of servedRegistrations(routes)) {
137
+ for (const method of service.methods) {
138
+ if (method.localName in implementation) {
139
+ methods.push(method);
140
+ }
141
+ }
142
+ }
143
+ return methods;
144
+ }
145
+
112
146
  /** A gRPC transport to the composed server on `port`, presenting `token` when given. */
113
147
  export function transportFor(port: number, token?: string): Transport {
114
148
  return createGrpcTransport({
@@ -6,10 +6,20 @@
6
6
  * `create` RPC's admission rule (2a A7) and the three IamPolicy system
7
7
  * RPCs' (Q-OR-7). A wire `user`, `runner` or a composition's own class
8
8
  * (guest) is never one.
9
+ *
10
+ * Also pins `isServerComposedRequest`, the narrower predicate the label
11
+ * guard, the memory-capture gate, the execution-context create check and
12
+ * the default-instance arm of the instance creates share: `internal` or an
13
+ * in-process origin, and never a wire `machine` account — the one row
14
+ * where the two predicates part, pinned so a step trusting server-composed
15
+ * state can never be widened to the wire by picking the wrong one.
9
16
  */
10
17
  import { describe, expect, it } from "vitest";
11
18
 
12
- import { isPlatformPipelineCaller } from "../identity.js";
19
+ import {
20
+ isPlatformPipelineCaller,
21
+ isServerComposedRequest,
22
+ } from "../identity.js";
13
23
  import type { CallerIdentity } from "../identity.js";
14
24
 
15
25
  function caller(fields: Partial<CallerIdentity>): CallerIdentity {
@@ -60,3 +70,36 @@ describe("isPlatformPipelineCaller", () => {
60
70
  );
61
71
  });
62
72
  });
73
+
74
+ describe("isServerComposedRequest", () => {
75
+ it("is true for the internal class and for any identity that entered in-process", () => {
76
+ expect(isServerComposedRequest(caller({ callerClass: "internal" }))).toBe(
77
+ true,
78
+ );
79
+ expect(
80
+ isServerComposedRequest(
81
+ caller({ callerClass: "user", origin: "in-process" }),
82
+ ),
83
+ ).toBe(true);
84
+ expect(
85
+ isServerComposedRequest(
86
+ caller({ callerClass: "channel", origin: "in-process" }),
87
+ ),
88
+ ).toBe(true);
89
+ });
90
+
91
+ it("is false for every wire caller, the machine class included — the row where it parts from isPlatformPipelineCaller", () => {
92
+ const wireMachine = caller({ callerClass: "machine" });
93
+ expect(isServerComposedRequest(wireMachine)).toBe(false);
94
+ expect(isPlatformPipelineCaller(wireMachine)).toBe(true);
95
+ expect(isServerComposedRequest(caller({ callerClass: "user" }))).toBe(
96
+ false,
97
+ );
98
+ expect(
99
+ isServerComposedRequest(caller({ callerClass: "user", origin: "wire" })),
100
+ ).toBe(false);
101
+ expect(isServerComposedRequest(caller({ callerClass: "runner" }))).toBe(
102
+ false,
103
+ );
104
+ });
105
+ });
@@ -12,7 +12,14 @@
12
12
  * - search (enumeration verb feeding the engine allowlist, and the
13
13
  * crossOrgPublic bypass where FGA is never consulted — lane 21),
14
14
  * - the OUTAGE arm: a throwing scope answers the sanitized INTERNAL,
15
- * never an empty (or full!) list.
15
+ * never an empty (or full!) list;
16
+ * - the SERVER'S OWN reads over `inProcessTransport` (stigmer#1207):
17
+ * the bare in-process call is stamped `internal` and the helper
18
+ * answers it before the scope — agentexecution.listBySession (the
19
+ * lane every server-internal reader of a session's turns rides) and
20
+ * workflowexecution.list with the org arm still applied; a caller
21
+ * PROPAGATED through the in-process header keeps its class and is
22
+ * narrowed exactly like the wire.
16
23
  *
17
24
  * Per-lane logic beyond the wiring is pinned in the helper matrix
18
25
  * (list-read-scope.test.ts), the summaries suite, and the store-contract
@@ -31,6 +38,8 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
31
38
 
32
39
  import { ActivityQueryController } from "@stigmer/protos/ai/stigmer/activity/v1/query_pb";
33
40
  import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
41
+ import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
42
+ import { AgentExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/query_pb";
34
43
  import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
35
44
  import { SessionQueryController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/query_pb";
36
45
  import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
@@ -44,6 +53,11 @@ import { loadConfig } from "../../boot/config.js";
44
53
  import { composeServer } from "../../boot/compose.js";
45
54
  import type { ComposedServer } from "../../boot/compose.js";
46
55
  import { createLogger } from "../../boot/logger.js";
56
+ import { testCallerIdentity } from "../../pipeline/__tests__/support.js";
57
+ import {
58
+ IN_PROCESS_CALLER_HEADER,
59
+ encodeInProcessCaller,
60
+ } from "../../pipeline/interceptors/auth.js";
47
61
  import type { ListReadScope } from "../list-read-scope.js";
48
62
  import type { ServerExtension } from "../registry.js";
49
63
 
@@ -133,6 +147,27 @@ describe("list read scope (composed server, fake scope)", () => {
133
147
  }),
134
148
  );
135
149
  }
150
+ // Two turns of one session — the rows a server-internal reader of a
151
+ // conversation's executions asks listBySession for.
152
+ for (const id of ["aex_turn_1", "aex_turn_2"]) {
153
+ await server.store.saveResource(
154
+ ApiResourceKind.agent_execution,
155
+ id,
156
+ AgentExecutionSchema,
157
+ create(AgentExecutionSchema, {
158
+ apiVersion: "agentic.stigmer.ai/v1",
159
+ kind: "AgentExecution",
160
+ metadata: { id, name: id, org: "acme" },
161
+ spec: { sessionId: "ses_mine" },
162
+ status: {
163
+ audit: {
164
+ specAudit: { createdAt: { seconds: 1_700_000_000n } },
165
+ statusAudit: { updatedAt: { seconds: 1_700_000_000n } },
166
+ },
167
+ },
168
+ }),
169
+ );
170
+ }
136
171
  for (const [id, org] of [
137
172
  ["wfe_mine", "acme"],
138
173
  ["wfe_other_org", "rival"],
@@ -273,4 +308,55 @@ describe("list read scope (composed server, fake scope)", () => {
273
308
  const list = await query.list({});
274
309
  expect(list.entries).toEqual([]);
275
310
  });
311
+
312
+ describe("the server's own reads over the in-process transport (stigmer#1207)", () => {
313
+ it("agentexecution.listBySession, bare: every turn of the session, the scope never asked", async () => {
314
+ // The scope would hide everything if it were asked.
315
+ allowed = new Set();
316
+ const query = createClient(
317
+ AgentExecutionQueryController,
318
+ server.inProcessTransport,
319
+ );
320
+ const turns = await query.listBySession({ sessionId: "ses_mine" });
321
+ expect(turns.entries.map((e) => e.metadata?.id).sort()).toEqual([
322
+ "aex_turn_1",
323
+ "aex_turn_2",
324
+ ]);
325
+ expect(seenKinds).toEqual([]);
326
+ });
327
+
328
+ it("a caller propagated through the in-process header keeps its class and is narrowed like the wire", async () => {
329
+ allowed = new Set(["aex_turn_2"]);
330
+ const query = createClient(
331
+ AgentExecutionQueryController,
332
+ server.inProcessTransport,
333
+ );
334
+ const turns = await query.listBySession(
335
+ { sessionId: "ses_mine" },
336
+ {
337
+ headers: {
338
+ [IN_PROCESS_CALLER_HEADER]: encodeInProcessCaller(
339
+ testCallerIdentity({ identityId: "alice" }),
340
+ ),
341
+ },
342
+ },
343
+ );
344
+ expect(turns.entries.map((e) => e.metadata?.id)).toEqual(["aex_turn_2"]);
345
+ expect(seenKinds).toEqual([ApiResourceKind.agent_execution]);
346
+ });
347
+
348
+ it("workflowexecution.list, bare: the request's org predicate still applies, the scope never asked", async () => {
349
+ allowed = new Set();
350
+ const query = createClient(
351
+ WorkflowExecutionQueryController,
352
+ server.inProcessTransport,
353
+ );
354
+ const acme = await query.list({ org: "acme" });
355
+ expect(acme.entries.map((e) => e.metadata?.id).sort()).toEqual([
356
+ "wfe_foreign",
357
+ "wfe_mine",
358
+ ]);
359
+ expect(seenKinds).toEqual([]);
360
+ });
361
+ });
276
362
  });
@@ -12,6 +12,11 @@
12
12
  * 20260913.04 T02 the scope is the last per-row predicate, so it is
13
13
  * offered the org's rows and never the kind's — the same result set,
14
14
  * a fraction of the candidates);
15
+ * - the `internal` class — the server acting as itself — gets the
16
+ * org-narrowed rows and the scope is never asked (the Authorize
17
+ * step's trust-domain rule on a list answer, stigmer#1207); the rule
18
+ * is class-only, so a caller PROPAGATED through the in-process
19
+ * transport (its own class, origin in-process) is still narrowed;
15
20
  * - the candidates carry {id, org, labels} — the driver's guest
16
21
  * cookie rule keys on labels — and the row's AUTHORIZATION FACTS
17
22
  * (creator stamp, visibility level, every parent link), read by the
@@ -160,6 +165,64 @@ describe("restrictListByReadScope", () => {
160
165
  ]);
161
166
  });
162
167
 
168
+ describe("the internal class — the server acting as itself (stigmer#1207)", () => {
169
+ /** A scope that must never be reached: any call is the failure under test. */
170
+ const neverAsked: ListReadScope = {
171
+ authorizedResourceIds: () =>
172
+ Promise.reject(new Error("the scope was asked for the internal class")),
173
+ restrictListEntries: () =>
174
+ Promise.reject(new Error("the scope was asked for the internal class")),
175
+ };
176
+ const internal = testCallerIdentity({
177
+ identityId: "system",
178
+ callerClass: "internal",
179
+ origin: "in-process",
180
+ });
181
+
182
+ it("gets the org-narrowed rows in the lane's order; the scope is never asked", async () => {
183
+ const withOrg = await restrictListByReadScope(
184
+ neverAsked,
185
+ internal,
186
+ ApiResourceKind.session,
187
+ rows,
188
+ "acme",
189
+ );
190
+ // The request's org predicate still applies — it is the request's,
191
+ // not the caller's; only the caller predicate is skipped.
192
+ expect(withOrg.map((r) => r.metadata.id)).toEqual(["ses_a", "ses_b"]);
193
+ const blankOrg = await restrictListByReadScope(
194
+ neverAsked,
195
+ internal,
196
+ ApiResourceKind.session,
197
+ rows,
198
+ "",
199
+ );
200
+ expect(blankOrg.map((r) => r.metadata.id)).toEqual([
201
+ "ses_a",
202
+ "ses_b",
203
+ "ses_c",
204
+ ]);
205
+ // A fresh array either way, as every arm of the helper returns.
206
+ expect(blankOrg).not.toBe(rows);
207
+ });
208
+
209
+ it("the rule is class-only: a caller propagated through the in-process transport keeps its class and is still narrowed", async () => {
210
+ const propagated = testCallerIdentity({
211
+ identityId: "alice",
212
+ callerClass: "user",
213
+ origin: "in-process",
214
+ });
215
+ const kept = await restrictListByReadScope(
216
+ scopeKeeping(["ses_c"]),
217
+ propagated,
218
+ ApiResourceKind.session,
219
+ rows,
220
+ "",
221
+ );
222
+ expect(kept.map((r) => r.metadata.id)).toEqual(["ses_c"]);
223
+ });
224
+ });
225
+
163
226
  it("an empty kept set is a real answer — the empty list", async () => {
164
227
  const kept = await restrictListByReadScope(
165
228
  scopeKeeping([]),
@@ -112,9 +112,27 @@ export interface IdentityVerifier {
112
112
  * rule is load-bearing in open source.
113
113
  */
114
114
  export function isPlatformPipelineCaller(caller: CallerIdentity): boolean {
115
- return (
116
- caller.callerClass === "machine" ||
117
- caller.callerClass === "internal" ||
118
- caller.origin === "in-process"
119
- );
115
+ return caller.callerClass === "machine" || isServerComposedRequest(caller);
116
+ }
117
+
118
+ /**
119
+ * Whether the request `caller` arrived on was composed by the server's own
120
+ * code rather than presented on the wire: the `internal` class (the server
121
+ * acting as itself) or ANY identity that entered through the in-process
122
+ * transport (a propagated person the server is building a request for,
123
+ * such as the default-instance self-heal that creates as the run's human
124
+ * for attribution). The serving chassis strips the propagation header, so
125
+ * the wire cannot claim this.
126
+ *
127
+ * This is the trust arm the label guard, the memory-capture gate and the
128
+ * execution-context create check share: what a server-composed request
129
+ * carries was decided by the service code that built it, and the
130
+ * entry-point request already passed its own gate. It is NARROWER than
131
+ * `isPlatformPipelineCaller`: a wire `machine` account is one of the
132
+ * platform's pipelines but its request is still the wire's, so a step
133
+ * that trusts server-composed state (a reserved label, a default
134
+ * instance's provenance) must key on this predicate and never on that one.
135
+ */
136
+ export function isServerComposedRequest(caller: CallerIdentity): boolean {
137
+ return caller.callerClass === "internal" || caller.origin === "in-process";
120
138
  }
@@ -48,11 +48,27 @@
48
48
  * authorization-backend outage surfaces as the pipeline's sanitized
49
49
  * INTERNAL, exactly the Java baseline (DD-007: unavailable is never
50
50
  * softened).
51
- * - The scope receives whatever identity the call carries, including
52
- * the in-process `internal` class — the driver owns that arm's
53
- * semantics (the Java baseline propagates the original caller
54
- * through in-process calls, so a composed driver normally never
55
- * sees `internal` on these lanes).
51
+ * - THE `internal` CLASS IS ANSWERED HERE, NEVER OFFERED TO
52
+ * `restrictListEntries`. The server acting as itself over the
53
+ * in-process transport (boot/inprocess.ts mints the class; nothing
54
+ * else can) is the server's own trust domain, on list lanes exactly
55
+ * as on the Authorize step, which returns for the class before any
56
+ * Authorizer is asked (pipeline/steps/authorize.ts). The helper
57
+ * below returns the org-narrowed rows for that class before the
58
+ * scope is consulted. The rule is class-only, like the step's: a
59
+ * caller PROPAGATED through the in-process header keeps its own
60
+ * class and stays scoped. Why here and not in a driver: a driver
61
+ * answering "every offered id" for the class must be written once
62
+ * per edition and is a silent short list the day one edition
63
+ * forgets (stigmer#1207: a composed driver scoped the class and
64
+ * every server-internal list read came back empty — the
65
+ * personal-environment reads at execution-context creation and MCP
66
+ * connect in this repository, and a composition's own in-process
67
+ * readers of a session's executions).
68
+ * The enumeration verb is a wire caller's verb: no in-process edge
69
+ * reaches `authorizedResourceIds`, and only a scanning driver can
70
+ * say "all", so that verb's internal arm stays the driver's and is
71
+ * stated in its doc below.
56
72
  * - THE SCOPE IS THE LAST PER-ROW PREDICATE IN A LANE (stigmer-cloud
57
73
  * 20260913.04 T02, Q-LB-11). Every request predicate a lane applies
58
74
  * — the request's org, a phase, labels, a parent id, filter criteria
@@ -157,6 +173,13 @@ export interface ListReadScope {
157
173
  * answer — consumers map it to their lane's empty shape without
158
174
  * touching the store. A scope may throw on a kind it was never ruled
159
175
  * to serve (a consumer bug by contract).
176
+ *
177
+ * The `internal` class CAN reach this verb (no helper stands between a
178
+ * consumer and it), and the driver owns that arm: a scanning driver
179
+ * answers every id of the kind, an engine-backed one answers what its
180
+ * engine holds for the class. No in-process edge reaches an
181
+ * enumeration lane today, so the two answers have never met a caller;
182
+ * a consumer that adds one reads this line first.
160
183
  */
161
184
  authorizedResourceIds(
162
185
  caller: CallerIdentity,
@@ -168,6 +191,12 @@ export interface ListReadScope {
168
191
  * baseline: the authorized-ids intersection plus the guest
169
192
  * cookie-label rule where the lane carries it). The result must be a
170
193
  * subset of the offered ids — a scope only narrows.
194
+ *
195
+ * `caller` is never the `internal` class: `restrictListByReadScope`,
196
+ * the one consumption idiom, answers that class before any driver is
197
+ * asked (the header's contract line). A driver that is offered it has
198
+ * been reached around the idiom and should refuse loudly rather than
199
+ * evaluate the server as a person (the built-in driver does).
171
200
  */
172
201
  restrictListEntries(
173
202
  caller: CallerIdentity,
@@ -209,8 +238,13 @@ export interface ScopedListResource {
209
238
  * permission-bounded across orgs, verified per lane in the entry's
210
239
  * census — lanes Java does not org-narrow pass ""), then to the kept
211
240
  * ids. The same set either way (both are per-row predicates); the
212
- * order is the header's second-to-last contract line: the scope sees
213
- * the org's rows, not the kind's;
241
+ * order is the header's contract line: the scope sees the org's
242
+ * rows, not the kind's;
243
+ * - scope composed, caller `internal` → the org-narrowed rows, the
244
+ * scope never asked and no candidate built (the header's contract
245
+ * line). The org predicate is the REQUEST's, not the caller's, so
246
+ * the server's own read of a cross-org lane still honours the org
247
+ * it asked for; only the caller predicate is skipped;
214
248
  * - every candidate carries the row's authorization facts, and the
215
249
  * candidates of a kind whose authorization is its parent's name that
216
250
  * parent again as `authorizationParent` — one of the facts' parent
@@ -231,6 +265,12 @@ export async function restrictListByReadScope<T extends ScopedListResource>(
231
265
  requestOrg === ""
232
266
  ? resources
233
267
  : resources.filter((resource) => resource.metadata?.org === requestOrg);
268
+ // The server acting as itself: the trust-domain rule of the Authorize
269
+ // step, applied to a list answer — the caller predicate is skipped, the
270
+ // request's org predicate above is not (the header's contract line).
271
+ if (caller.callerClass === "internal") {
272
+ return [...offered];
273
+ }
234
274
  const parentOf = authorizationParentReader(kind);
235
275
  const entries: ListEntryMeta[] = offered.map((resource) => {
236
276
  const entry: ListEntryMeta = {
@@ -23,8 +23,8 @@ import type { ApiResourceMetadata } from "@stigmer/protos/ai/stigmer/commons/api
23
23
 
24
24
  /**
25
25
  * The platform-reserved label key namespace. Keys under this prefix carry
26
- * platform semantics (default-agent resolution, personal-environment
27
- * uniqueness, default-instance marking) and are written by the server —
26
+ * platform semantics (personal-environment uniqueness, default-instance
27
+ * marking, plugin membership and lineage) and are written by the server —
28
28
  * never introduced by ordinary client requests on the cloud edition.
29
29
  */
30
30
  export const RESERVED_LABEL_PREFIX = "stigmer.ai/";
@@ -100,8 +100,8 @@ export function grpcCodeName(code: Code): string {
100
100
  * errors.As branch keeps the INNER code but rewrites the message to the
101
101
  * full wrapped text — and the inner client error's Error() renders as
102
102
  * `rpc error: code = <CodeName> desc = <message>`. Byte-parity with Go on
103
- * the two arms that hit this (agent create's CreateDefaultInstance,
104
- * session create's ResolveDefaultAgentInstance). The transport-formatting
103
+ * the arms that hit this (agent create's CreateDefaultInstance, agent
104
+ * execution create's default-instance and session bootstrap). The transport-formatting
105
105
  * leak is filed as stigmer/stigmer#852 for a both-editions post-cutover
106
106
  * fix; until then this shim IS the wire contract.
107
107
  *