@stigmer/server 3.23.0 → 3.24.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 (272) hide show
  1. package/dist/authorization/authorizer.d.ts +4 -8
  2. package/dist/authorization/authorizer.d.ts.map +1 -1
  3. package/dist/authorization/authorizer.js +5 -9
  4. package/dist/authorization/authorizer.js.map +1 -1
  5. package/dist/authorization/derived-tuples.d.ts.map +1 -1
  6. package/dist/authorization/derived-tuples.js +10 -25
  7. package/dist/authorization/derived-tuples.js.map +1 -1
  8. package/dist/authorization/evaluator.d.ts +7 -8
  9. package/dist/authorization/evaluator.d.ts.map +1 -1
  10. package/dist/authorization/evaluator.js +4 -20
  11. package/dist/authorization/evaluator.js.map +1 -1
  12. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  13. package/dist/authorization/list-read-scope.js +5 -9
  14. package/dist/authorization/list-read-scope.js.map +1 -1
  15. package/dist/authorization/model/agent.d.ts.map +1 -1
  16. package/dist/authorization/model/agent.js +4 -5
  17. package/dist/authorization/model/agent.js.map +1 -1
  18. package/dist/authorization/model/agent_instance.d.ts.map +1 -1
  19. package/dist/authorization/model/agent_instance.js +2 -2
  20. package/dist/authorization/model/agent_instance.js.map +1 -1
  21. package/dist/authorization/model/mcp_server.d.ts.map +1 -1
  22. package/dist/authorization/model/mcp_server.js +2 -2
  23. package/dist/authorization/model/mcp_server.js.map +1 -1
  24. package/dist/authorization/model/plugin.d.ts.map +1 -1
  25. package/dist/authorization/model/plugin.js +3 -4
  26. package/dist/authorization/model/plugin.js.map +1 -1
  27. package/dist/authorization/model/rewrite.d.ts +8 -10
  28. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  29. package/dist/authorization/model/rewrite.js +0 -3
  30. package/dist/authorization/model/rewrite.js.map +1 -1
  31. package/dist/authorization/model/skill.d.ts.map +1 -1
  32. package/dist/authorization/model/skill.js +2 -2
  33. package/dist/authorization/model/skill.js.map +1 -1
  34. package/dist/authorization/model/workflow.d.ts.map +1 -1
  35. package/dist/authorization/model/workflow.js +2 -2
  36. package/dist/authorization/model/workflow.js.map +1 -1
  37. package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
  38. package/dist/authorization/model/workflow_instance.js +2 -2
  39. package/dist/authorization/model/workflow_instance.js.map +1 -1
  40. package/dist/authorization/organization-directory.d.ts.map +1 -1
  41. package/dist/authorization/organization-directory.js +2 -3
  42. package/dist/authorization/organization-directory.js.map +1 -1
  43. package/dist/authorization/store-test-kit.d.ts +0 -6
  44. package/dist/authorization/store-test-kit.d.ts.map +1 -1
  45. package/dist/authorization/store-test-kit.js +16 -62
  46. package/dist/authorization/store-test-kit.js.map +1 -1
  47. package/dist/authorization/tuples.d.ts +13 -32
  48. package/dist/authorization/tuples.d.ts.map +1 -1
  49. package/dist/authorization/tuples.js +3 -11
  50. package/dist/authorization/tuples.js.map +1 -1
  51. package/dist/domain/agent/controller.d.ts.map +1 -1
  52. package/dist/domain/agent/controller.js +4 -4
  53. package/dist/domain/agent/controller.js.map +1 -1
  54. package/dist/domain/agentchannel/controller.d.ts.map +1 -1
  55. package/dist/domain/agentchannel/controller.js +3 -1
  56. package/dist/domain/agentchannel/controller.js.map +1 -1
  57. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  58. package/dist/domain/agentexecution/controller.js +3 -1
  59. package/dist/domain/agentexecution/controller.js.map +1 -1
  60. package/dist/domain/agentinstance/controller.d.ts.map +1 -1
  61. package/dist/domain/agentinstance/controller.js +3 -4
  62. package/dist/domain/agentinstance/controller.js.map +1 -1
  63. package/dist/domain/agentshare/constants.d.ts +6 -11
  64. package/dist/domain/agentshare/constants.d.ts.map +1 -1
  65. package/dist/domain/agentshare/constants.js +8 -16
  66. package/dist/domain/agentshare/constants.js.map +1 -1
  67. package/dist/domain/agentshare/controller.d.ts.map +1 -1
  68. package/dist/domain/agentshare/controller.js +3 -1
  69. package/dist/domain/agentshare/controller.js.map +1 -1
  70. package/dist/domain/agentshare/steps.d.ts +18 -25
  71. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  72. package/dist/domain/agentshare/steps.js +41 -142
  73. package/dist/domain/agentshare/steps.js.map +1 -1
  74. package/dist/domain/environment/controller.d.ts.map +1 -1
  75. package/dist/domain/environment/controller.js +2 -3
  76. package/dist/domain/environment/controller.js.map +1 -1
  77. package/dist/domain/executioncontext/controller.d.ts.map +1 -1
  78. package/dist/domain/executioncontext/controller.js +2 -1
  79. package/dist/domain/executioncontext/controller.js.map +1 -1
  80. package/dist/domain/mcpserver/controller.d.ts.map +1 -1
  81. package/dist/domain/mcpserver/controller.js +3 -4
  82. package/dist/domain/mcpserver/controller.js.map +1 -1
  83. package/dist/domain/plugin/controller.d.ts.map +1 -1
  84. package/dist/domain/plugin/controller.js +1 -3
  85. package/dist/domain/plugin/controller.js.map +1 -1
  86. package/dist/domain/plugin/push.d.ts +7 -5
  87. package/dist/domain/plugin/push.d.ts.map +1 -1
  88. package/dist/domain/plugin/push.js +8 -14
  89. package/dist/domain/plugin/push.js.map +1 -1
  90. package/dist/domain/schedule/controller.d.ts.map +1 -1
  91. package/dist/domain/schedule/controller.js +3 -1
  92. package/dist/domain/schedule/controller.js.map +1 -1
  93. package/dist/domain/session/controller.d.ts.map +1 -1
  94. package/dist/domain/session/controller.js +3 -2
  95. package/dist/domain/session/controller.js.map +1 -1
  96. package/dist/domain/skill/controller.d.ts.map +1 -1
  97. package/dist/domain/skill/controller.js +0 -2
  98. package/dist/domain/skill/controller.js.map +1 -1
  99. package/dist/domain/workflow/agent-call-references.d.ts +81 -0
  100. package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
  101. package/dist/domain/workflow/agent-call-references.js +117 -0
  102. package/dist/domain/workflow/agent-call-references.js.map +1 -0
  103. package/dist/domain/workflow/controller.d.ts.map +1 -1
  104. package/dist/domain/workflow/controller.js +14 -6
  105. package/dist/domain/workflow/controller.js.map +1 -1
  106. package/dist/domain/workflow/registry/data/task-kind-registry.json +3 -3
  107. package/dist/domain/workflow/validation/task-config-constraints.d.ts +10 -1
  108. package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
  109. package/dist/domain/workflow/validation/task-config-constraints.js +3 -2
  110. package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
  111. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  112. package/dist/domain/workflowexecution/controller.js +4 -2
  113. package/dist/domain/workflowexecution/controller.js.map +1 -1
  114. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  115. package/dist/domain/workflowinstance/controller.js +3 -4
  116. package/dist/domain/workflowinstance/controller.js.map +1 -1
  117. package/dist/extensions/authorization-queries.d.ts +6 -5
  118. package/dist/extensions/authorization-queries.d.ts.map +1 -1
  119. package/dist/extensions/list-read-scope.d.ts +1 -1
  120. package/dist/extensions/resource-authorization.d.ts +3 -2
  121. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  122. package/dist/index.d.ts +1 -1
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js.map +1 -1
  125. package/dist/pipeline/apiresource-labels.d.ts +10 -9
  126. package/dist/pipeline/apiresource-labels.d.ts.map +1 -1
  127. package/dist/pipeline/apiresource-labels.js +10 -9
  128. package/dist/pipeline/apiresource-labels.js.map +1 -1
  129. package/dist/pipeline/apiresource-meta.d.ts +12 -3
  130. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  131. package/dist/pipeline/apiresource-meta.js +21 -10
  132. package/dist/pipeline/apiresource-meta.js.map +1 -1
  133. package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
  134. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  135. package/dist/pipeline/steps/authorization-tuples.js +9 -11
  136. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  137. package/dist/pipeline/steps/references.d.ts +204 -20
  138. package/dist/pipeline/steps/references.d.ts.map +1 -1
  139. package/dist/pipeline/steps/references.js +356 -43
  140. package/dist/pipeline/steps/references.js.map +1 -1
  141. package/dist/query/search/controller.js +0 -1
  142. package/dist/query/search/controller.js.map +1 -1
  143. package/dist/query/search/criteria.d.ts +1 -5
  144. package/dist/query/search/criteria.d.ts.map +1 -1
  145. package/dist/query/search/criteria.js +3 -13
  146. package/dist/query/search/criteria.js.map +1 -1
  147. package/dist/query/search/handler.d.ts +5 -4
  148. package/dist/query/search/handler.d.ts.map +1 -1
  149. package/dist/query/search/handler.js +7 -6
  150. package/dist/query/search/handler.js.map +1 -1
  151. package/dist/query/search/query-store.d.ts.map +1 -1
  152. package/dist/query/search/query-store.js +0 -2
  153. package/dist/query/search/query-store.js.map +1 -1
  154. package/dist/store/interface.d.ts +3 -6
  155. package/dist/store/interface.d.ts.map +1 -1
  156. package/dist/store/interface.js.map +1 -1
  157. package/dist/store/postgres/migrations.d.ts +3 -1
  158. package/dist/store/postgres/migrations.d.ts.map +1 -1
  159. package/dist/store/postgres/migrations.js +35 -1
  160. package/dist/store/postgres/migrations.js.map +1 -1
  161. package/dist/store/postgres/store.d.ts.map +1 -1
  162. package/dist/store/postgres/store.js +4 -9
  163. package/dist/store/postgres/store.js.map +1 -1
  164. package/dist/store/public-visibility-retired.d.ts +67 -0
  165. package/dist/store/public-visibility-retired.d.ts.map +1 -0
  166. package/dist/store/public-visibility-retired.js +55 -0
  167. package/dist/store/public-visibility-retired.js.map +1 -0
  168. package/dist/store/sqlite/migrations.d.ts +3 -1
  169. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  170. package/dist/store/sqlite/migrations.js +36 -1
  171. package/dist/store/sqlite/migrations.js.map +1 -1
  172. package/dist/store/sqlite/store.d.ts.map +1 -1
  173. package/dist/store/sqlite/store.js +4 -8
  174. package/dist/store/sqlite/store.js.map +1 -1
  175. package/package.json +6 -6
  176. package/src/authorization/README.md +24 -26
  177. package/src/authorization/__tests__/authorizer.test.ts +10 -9
  178. package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
  179. package/src/authorization/__tests__/evaluator.test.ts +14 -73
  180. package/src/authorization/__tests__/facts.test.ts +2 -2
  181. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +19 -27
  182. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
  183. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
  184. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +1 -15
  185. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
  186. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
  187. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
  188. package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
  189. package/src/authorization/__tests__/store-tests.test.ts +2 -6
  190. package/src/authorization/__tests__/tuples.test.ts +12 -11
  191. package/src/authorization/authorizer.ts +4 -9
  192. package/src/authorization/derived-tuples.ts +14 -31
  193. package/src/authorization/evaluator.ts +8 -44
  194. package/src/authorization/list-read-scope.ts +5 -11
  195. package/src/authorization/model/agent.ts +2 -5
  196. package/src/authorization/model/agent_instance.ts +0 -2
  197. package/src/authorization/model/mcp_server.ts +0 -2
  198. package/src/authorization/model/plugin.ts +1 -4
  199. package/src/authorization/model/rewrite.ts +8 -13
  200. package/src/authorization/model/skill.ts +0 -2
  201. package/src/authorization/model/workflow.ts +0 -2
  202. package/src/authorization/model/workflow_instance.ts +0 -2
  203. package/src/authorization/organization-directory.ts +1 -3
  204. package/src/authorization/store-test-kit.ts +16 -75
  205. package/src/authorization/tuples.ts +16 -39
  206. package/src/domain/agent/__tests__/agent.test.ts +11 -4
  207. package/src/domain/agent/controller.ts +7 -7
  208. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
  209. package/src/domain/agentchannel/controller.ts +6 -1
  210. package/src/domain/agentexecution/controller.ts +6 -1
  211. package/src/domain/agentinstance/controller.ts +6 -12
  212. package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
  213. package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
  214. package/src/domain/agentshare/constants.ts +14 -26
  215. package/src/domain/agentshare/controller.ts +6 -1
  216. package/src/domain/agentshare/steps.ts +41 -194
  217. package/src/domain/environment/controller.ts +5 -8
  218. package/src/domain/executioncontext/controller.ts +5 -1
  219. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
  220. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
  221. package/src/domain/mcpserver/controller.ts +6 -12
  222. package/src/domain/plugin/controller.ts +1 -8
  223. package/src/domain/plugin/push.ts +8 -16
  224. package/src/domain/schedule/controller.ts +6 -1
  225. package/src/domain/session/controller.ts +12 -4
  226. package/src/domain/skill/controller.ts +0 -7
  227. package/src/domain/workflow/__tests__/agent-call-references.test.ts +373 -0
  228. package/src/domain/workflow/agent-call-references.ts +204 -0
  229. package/src/domain/workflow/controller.ts +28 -10
  230. package/src/domain/workflow/registry/data/task-kind-registry.json +3 -3
  231. package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
  232. package/src/domain/workflowexecution/controller.ts +7 -2
  233. package/src/domain/workflowinstance/controller.ts +6 -12
  234. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
  235. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  236. package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
  237. package/src/extensions/authorization-queries.ts +6 -5
  238. package/src/extensions/list-read-scope.ts +1 -1
  239. package/src/extensions/resource-authorization.ts +3 -5
  240. package/src/index.ts +0 -1
  241. package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
  242. package/src/pipeline/apiresource-labels.ts +10 -9
  243. package/src/pipeline/apiresource-meta.ts +21 -10
  244. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
  245. package/src/pipeline/steps/__tests__/references.test.ts +737 -0
  246. package/src/pipeline/steps/authorization-tuples.ts +9 -11
  247. package/src/pipeline/steps/references.ts +622 -70
  248. package/src/query/search/__tests__/criteria.test.ts +0 -4
  249. package/src/query/search/__tests__/query-store.test.ts +11 -40
  250. package/src/query/search/controller.ts +0 -1
  251. package/src/query/search/criteria.ts +0 -14
  252. package/src/query/search/handler.ts +6 -7
  253. package/src/query/search/query-store.ts +0 -2
  254. package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
  255. package/src/store/__tests__/store-contract.ts +6 -51
  256. package/src/store/interface.ts +3 -6
  257. package/src/store/postgres/__tests__/migrations.test.ts +187 -6
  258. package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
  259. package/src/store/postgres/migrations.ts +48 -1
  260. package/src/store/postgres/store.ts +4 -11
  261. package/src/store/public-visibility-retired.ts +119 -0
  262. package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
  263. package/src/store/sqlite/migrations.ts +45 -1
  264. package/src/store/sqlite/store.ts +4 -10
  265. package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
  266. package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
  267. package/dist/pipeline/steps/visibility-gates.js +0 -139
  268. package/dist/pipeline/steps/visibility-gates.js.map +0 -1
  269. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
  270. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
  271. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
  272. package/src/pipeline/steps/visibility-gates.ts +0 -174
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Pins the workflow domain's collector for the reference rule
3
+ * (../agent-call-references.ts): the classifier's four forms (a literal
4
+ * `org/slug`, a relative bare slug, a value holding `${` that is fixed
5
+ * only at run, anything else malformed) and the refusal copy; the walk
6
+ * over top-level, nested and compensate tasks in declaration order, the
7
+ * relative form filled with the workflow's organization and marked, a
8
+ * run-time value collected as nothing; a config that does not decode
9
+ * skipped rather than double-reported; the step over a real store
10
+ * answering the rule's verdicts — a missing agent with the rule's copy,
11
+ * another organization's org-visible agent with the one
12
+ * cross-organization sentence, a platform-visible one admitted, the floor
13
+ * for a literal, the floor capped at org for a bare slug, a run-time value
14
+ * saved with no agent behind it; and the workflow's escalation door
15
+ * applying the same cap when a workflow calling a bare slug is raised to
16
+ * platform.
17
+ */
18
+ import { create } from "@bufbuild/protobuf";
19
+ import { Code, ConnectError } from "@connectrpc/connect";
20
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
21
+
22
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
23
+ import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
24
+ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
25
+ import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
26
+ import type { WorkflowSpec } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
27
+ import { WorkflowSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
28
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
29
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
30
+ import { UpdateVisibilityInputSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
31
+
32
+ import { RequestContext } from "../../../pipeline/request-context.js";
33
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
34
+ import {
35
+ belowFloorMessage,
36
+ missingReferencesMessage,
37
+ newGuardReferenceFloorOnEscalationStep,
38
+ notAvailableReferenceMessage,
39
+ referenceTargetKind,
40
+ } from "../../../pipeline/steps/references.js";
41
+ import type { SqliteStore } from "../../../store/sqlite/store.js";
42
+ import { tempStore } from "../../../store/sqlite/__tests__/support.js";
43
+ import {
44
+ classifyAgentReference,
45
+ collectAgentCallReferences,
46
+ malformedAgentReferenceMessage,
47
+ newValidateAgentCallReferencesStep,
48
+ } from "../agent-call-references.js";
49
+
50
+ const V = ApiResourceVisibility;
51
+ const AGENT = referenceTargetKind(ApiResourceKind.agent)!;
52
+
53
+ function agentCall(name: string, agent: string) {
54
+ return {
55
+ name,
56
+ kind: WorkflowTaskKind.agent_call,
57
+ taskConfig: { agent, message: "do the thing" },
58
+ };
59
+ }
60
+
61
+ function specOf(tasks: ReadonlyArray<Record<string, unknown>>): WorkflowSpec {
62
+ return create(WorkflowSpecSchema, { tasks } as never);
63
+ }
64
+
65
+ /** The three shapes the runner evaluates, whole and embedded, as authors write them. */
66
+ const RUN_TIME_VALUES = [
67
+ "${.env_vars.TEAM_ORG}/assistant",
68
+ "${.secrets.AGENT_REF}",
69
+ '${ "acme/reviewer" }',
70
+ "${ .input.agent }",
71
+ "acme/${ .input.slug }",
72
+ "${ .input.org }/a/b",
73
+ ];
74
+
75
+ describe("classifyAgentReference", () => {
76
+ it("reads `org/slug` as a literal and a bare slug as relative", () => {
77
+ expect(classifyAgentReference("acme/reviewer")).toEqual({
78
+ kind: "literal",
79
+ org: "acme",
80
+ slug: "reviewer",
81
+ });
82
+ expect(classifyAgentReference("reviewer")).toEqual({
83
+ kind: "relative",
84
+ slug: "reviewer",
85
+ });
86
+ });
87
+
88
+ it("reads any value holding `${` as fixed only at run, whatever its shape", () => {
89
+ for (const value of RUN_TIME_VALUES) {
90
+ expect(classifyAgentReference(value), value).toEqual({
91
+ kind: "run-time",
92
+ });
93
+ }
94
+ });
95
+
96
+ it("reads everything else as malformed", () => {
97
+ for (const malformed of ["", "/reviewer", "acme/", "a/b/c", "//"]) {
98
+ expect(classifyAgentReference(malformed), malformed).toEqual({
99
+ kind: "malformed",
100
+ });
101
+ }
102
+ });
103
+ });
104
+
105
+ describe("collectAgentCallReferences", () => {
106
+ it("collects top-level, nested and compensate agent_call tasks in declaration order, a bare slug filled with the workflow's organization and marked relative", () => {
107
+ const spec = specOf([
108
+ agentCall("first", "reviewer"),
109
+ {
110
+ name: "loop",
111
+ kind: WorkflowTaskKind.for_each,
112
+ taskConfig: {
113
+ each: "item",
114
+ in: "${ .items }",
115
+ // A nested task inside the Struct is JSON: proto field names.
116
+ do: [
117
+ {
118
+ name: "inner",
119
+ kind: "agent_call",
120
+ task_config: { agent: "globex/summarizer", message: "sum" },
121
+ },
122
+ ],
123
+ },
124
+ compensate: [agentCall("undo", "cleaner")],
125
+ },
126
+ {
127
+ name: "pause",
128
+ kind: WorkflowTaskKind.wait,
129
+ taskConfig: { duration: {} },
130
+ },
131
+ ]);
132
+ expect(collectAgentCallReferences(spec, "acme")).toEqual([
133
+ {
134
+ kind: ApiResourceKind.agent,
135
+ org: "acme",
136
+ slug: "reviewer",
137
+ resolvesIn: "running-organization",
138
+ },
139
+ { kind: ApiResourceKind.agent, org: "globex", slug: "summarizer" },
140
+ {
141
+ kind: ApiResourceKind.agent,
142
+ org: "acme",
143
+ slug: "cleaner",
144
+ resolvesIn: "running-organization",
145
+ },
146
+ ]);
147
+ });
148
+
149
+ it("collects nothing for a value fixed only at run, beside the references it does collect", () => {
150
+ const spec = specOf([
151
+ ...RUN_TIME_VALUES.map((value, i) => agentCall(`dynamic_${i}`, value)),
152
+ agentCall("fixed", "globex/summarizer"),
153
+ ]);
154
+ expect(collectAgentCallReferences(spec, "acme")).toEqual([
155
+ { kind: ApiResourceKind.agent, org: "globex", slug: "summarizer" },
156
+ ]);
157
+ });
158
+
159
+ it("refuses a malformed agent string naming the task", () => {
160
+ const spec = specOf([agentCall("broken", "a/b/c")]);
161
+ expect(() => collectAgentCallReferences(spec, "acme")).toThrow(
162
+ malformedAgentReferenceMessage("broken", "a/b/c"),
163
+ );
164
+ let error: unknown;
165
+ try {
166
+ collectAgentCallReferences(spec, "acme");
167
+ } catch (e) {
168
+ error = e;
169
+ }
170
+ expect((error as ConnectError).code).toBe(Code.InvalidArgument);
171
+ });
172
+
173
+ it("skips a config that does not decode — the spec validation step has already refused it", () => {
174
+ const spec = specOf([
175
+ {
176
+ name: "odd",
177
+ kind: WorkflowTaskKind.agent_call,
178
+ taskConfig: { agent: "reviewer", not_a_field: true },
179
+ },
180
+ ]);
181
+ expect(collectAgentCallReferences(spec, "acme")).toEqual([]);
182
+ expect(collectAgentCallReferences(undefined, "acme")).toEqual([]);
183
+ });
184
+ });
185
+
186
+ describe("ValidateAgentCallReferences over a store", () => {
187
+ let store: SqliteStore;
188
+ let cleanup: () => Promise<void>;
189
+
190
+ beforeEach(() => {
191
+ const temp = tempStore();
192
+ store = temp.store;
193
+ cleanup = temp.cleanup;
194
+ });
195
+
196
+ afterEach(async () => {
197
+ await cleanup();
198
+ });
199
+
200
+ async function seedAgent(
201
+ id: string,
202
+ org: string,
203
+ slug: string,
204
+ visibility: ApiResourceVisibility,
205
+ ): Promise<void> {
206
+ await store.saveResource(
207
+ ApiResourceKind.agent,
208
+ id,
209
+ AgentSchema,
210
+ create(AgentSchema, {
211
+ apiVersion: "agentic.stigmer.ai/v1",
212
+ kind: "Agent",
213
+ metadata: { id, name: slug, slug, org, visibility },
214
+ spec: { instructions: "a conformant instruction body" },
215
+ }),
216
+ );
217
+ }
218
+
219
+ function workflowCalling(
220
+ visibility: ApiResourceVisibility,
221
+ ...agents: string[]
222
+ ): Workflow {
223
+ return create(WorkflowSchema, {
224
+ apiVersion: "agentic.stigmer.ai/v1",
225
+ kind: "Workflow",
226
+ metadata: { id: "wfl_1", name: "Pipeline", org: "acme", visibility },
227
+ spec: specOf(agents.map((agent, i) => agentCall(`call_${i}`, agent))),
228
+ });
229
+ }
230
+
231
+ async function run(workflow: Workflow): Promise<unknown> {
232
+ const ctx = new RequestContext(
233
+ WorkflowSchema,
234
+ workflow,
235
+ testCallerIdentity(),
236
+ ApiResourceKind.workflow,
237
+ );
238
+ try {
239
+ await newValidateAgentCallReferencesStep(store).execute(ctx);
240
+ return undefined;
241
+ } catch (error) {
242
+ return error;
243
+ }
244
+ }
245
+
246
+ it("a missing agent is refused with the rule's copy; an existing one passes", async () => {
247
+ const error = await run(workflowCalling(V.visibility_org, "reviewer"));
248
+ expect(error).toBeInstanceOf(ConnectError);
249
+ expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
250
+ expect((error as ConnectError).rawMessage).toBe(
251
+ missingReferencesMessage(AGENT, [{ slug: "reviewer", org: "acme" }]),
252
+ );
253
+ await seedAgent("agt_1", "acme", "reviewer", V.visibility_org);
254
+ expect(
255
+ await run(workflowCalling(V.visibility_org, "reviewer")),
256
+ ).toBeUndefined();
257
+ });
258
+
259
+ it("another organization's agent is admitted only at platform visibility, with the one sentence otherwise", async () => {
260
+ await seedAgent("agt_shared", "globex", "shared", V.visibility_platform);
261
+ await seedAgent("agt_internal", "globex", "internal", V.visibility_org);
262
+ expect(
263
+ await run(workflowCalling(V.visibility_org, "globex/shared")),
264
+ ).toBeUndefined();
265
+ const error = await run(
266
+ workflowCalling(V.visibility_org, "globex/internal"),
267
+ );
268
+ expect((error as ConnectError).rawMessage).toBe(
269
+ notAvailableReferenceMessage(AGENT, {
270
+ kind: ApiResourceKind.agent,
271
+ org: "globex",
272
+ slug: "internal",
273
+ }),
274
+ );
275
+ });
276
+
277
+ it("the floor holds for the agent a workflow calls: an org-visible workflow may not call a private agent", async () => {
278
+ await seedAgent("agt_mine", "acme", "mine", V.visibility_private);
279
+ const error = await run(workflowCalling(V.visibility_org, "mine"));
280
+ expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
281
+ expect((error as ConnectError).rawMessage).toContain(
282
+ "referenced agent 'acme/mine' is visibility_private while this resource is visibility_org",
283
+ );
284
+ });
285
+
286
+ it("a platform-visible workflow may call its organization's org-visible agent by bare slug, never by literal, and never a private one", async () => {
287
+ await seedAgent("agt_team", "acme", "team", V.visibility_org);
288
+ await seedAgent("agt_mine", "acme", "mine", V.visibility_private);
289
+ expect(
290
+ await run(workflowCalling(V.visibility_platform, "team")),
291
+ ).toBeUndefined();
292
+ // The literal names a fixed row that every organization's runs read.
293
+ const literal = await run(
294
+ workflowCalling(V.visibility_platform, "acme/team"),
295
+ );
296
+ expect((literal as ConnectError).rawMessage).toContain(
297
+ "referenced agent 'acme/team' is visibility_org while this resource is visibility_platform",
298
+ );
299
+ const below = await run(workflowCalling(V.visibility_platform, "mine"));
300
+ expect((below as ConnectError).rawMessage).toContain(
301
+ "referenced agent 'acme/mine' is visibility_private while this resource is visibility_platform",
302
+ );
303
+ });
304
+
305
+ it("a value fixed only at run saves with no agent behind it, at every level", async () => {
306
+ for (const visibility of [
307
+ V.visibility_private,
308
+ V.visibility_org,
309
+ V.visibility_platform,
310
+ ]) {
311
+ expect(
312
+ await run(workflowCalling(visibility, ...RUN_TIME_VALUES)),
313
+ V[visibility],
314
+ ).toBeUndefined();
315
+ }
316
+ });
317
+
318
+ describe("the workflow's escalation door", () => {
319
+ const TARGET = "loadedWorkflow";
320
+
321
+ async function escalate(
322
+ stored: Workflow,
323
+ requested: ApiResourceVisibility,
324
+ ): Promise<unknown> {
325
+ const ctx = new RequestContext(
326
+ UpdateVisibilityInputSchema,
327
+ create(UpdateVisibilityInputSchema, {
328
+ resourceId: "wfl_1",
329
+ visibility: requested,
330
+ }),
331
+ testCallerIdentity(),
332
+ ApiResourceKind.workflow,
333
+ );
334
+ ctx.set(TARGET, stored);
335
+ try {
336
+ await newGuardReferenceFloorOnEscalationStep(store, TARGET, [
337
+ (row) =>
338
+ collectAgentCallReferences(
339
+ (row as Workflow).spec,
340
+ (row as Workflow).metadata?.org ?? "",
341
+ ),
342
+ ]).execute(ctx);
343
+ return undefined;
344
+ } catch (error) {
345
+ return error;
346
+ }
347
+ }
348
+
349
+ it("raising a workflow that calls a bare slug to platform asks the capped floor", async () => {
350
+ await seedAgent("agt_team", "acme", "team", V.visibility_org);
351
+ await seedAgent("agt_mine", "acme", "mine", V.visibility_private);
352
+ expect(
353
+ await escalate(
354
+ workflowCalling(V.visibility_org, "team"),
355
+ V.visibility_platform,
356
+ ),
357
+ ).toBeUndefined();
358
+ const error = await escalate(
359
+ workflowCalling(V.visibility_private, "mine"),
360
+ V.visibility_platform,
361
+ );
362
+ expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
363
+ expect((error as ConnectError).rawMessage).toBe(
364
+ belowFloorMessage(
365
+ AGENT,
366
+ { kind: ApiResourceKind.agent, org: "acme", slug: "mine" },
367
+ V.visibility_private,
368
+ V.visibility_platform,
369
+ ),
370
+ );
371
+ });
372
+ });
373
+ });
@@ -0,0 +1,204 @@
1
+ /**
2
+ * ValidateAgentCallReferences — the second collector for the reference rule
3
+ * (pipeline/steps/references.ts): a workflow's `agent_call` tasks name their
4
+ * agent as a STRING inside a `google.protobuf.Struct` config the generic
5
+ * spec walker cannot see, so the rule would never meet them. This step
6
+ * parses each task's config through the converter's own strict decoder
7
+ * (`unmarshalTaskConfig`, the one reader of a task config), walks nested
8
+ * control-flow tasks exactly as the constraint validator does, turns every
9
+ * `agent` string the write can judge into a reference with the workflow as
10
+ * parent, and asks the rule's one function — same clauses, same copy. It
11
+ * lives in the workflow domain and not inside `InProcessValidator` because
12
+ * the validator is pure over the spec by design and a rule that reads the
13
+ * target's row cannot be.
14
+ *
15
+ * The string has three forms (the `agent` field's contract in
16
+ * agent_call.proto), told apart by `classifyAgentReference`:
17
+ *
18
+ * - `org/slug`, a LITERAL: judged now, like any stored reference;
19
+ * - a bare `slug`, RELATIVE: the runner resolves it in the organization
20
+ * the workflow runs in (`__stigmer_org_id`, the execution's), which is
21
+ * not the workflow's when a person runs it from another organization by
22
+ * id. It is judged now in the workflow's own organization with its
23
+ * floor capped at org (references.ts, "RELATIVE");
24
+ * - a value holding `${`, fixed only AT RUN: the runner's jq phase
25
+ * (`${ expr }`, whole or embedded) or its activity phase
26
+ * (`${.env_vars.KEY}`, `${.secrets.KEY}`) produces the reference when
27
+ * the task runs. The write collects nothing for it; the run reads the
28
+ * resolved agent as the person who ran the workflow, as it reads every
29
+ * id-bound binding. The slug grammar (metadata.proto) can never contain
30
+ * `${`, so the test is exact. It deliberately does not recognise only
31
+ * the runner's shapes: that would be a second copy of the runner's
32
+ * grammar in a package that shares only the protos with it, and a
33
+ * mistyped expression already fails at run with the read's not-found.
34
+ *
35
+ * Anything else is refused at write naming the task. The runner's own
36
+ * parser (`parseAgentReference` in runner/src/activities/call-agent.ts) is
37
+ * lenient — it splits on the first `/` and drops a third segment — and the
38
+ * CLI's `parseReference` (client-apps/cli/src/resources/reference.ts)
39
+ * refuses a leading slash; refusing every malformed literal here means
40
+ * neither ever meets one. The three cannot share code; the conformance
41
+ * suites are where their agreement is proven.
42
+ *
43
+ * Runs after NormalizeReferences and ValidateReferences on the workflow
44
+ * create and update chains, and feeds the workflow's escalation door
45
+ * (controller.ts). Proven by __tests__/agent-call-references.test.ts, the
46
+ * workflow conformance suite's agent_call reference arm, and the
47
+ * runner-as-subject execution arm for a reference fixed only at run.
48
+ */
49
+ import type { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
50
+ import type { AgentCallTaskConfig } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/tasks/agent_call_pb";
51
+ import type {
52
+ WorkflowSpec,
53
+ WorkflowTask,
54
+ } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
55
+ import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
56
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
57
+
58
+ import type { Store } from "../../store/interface.js";
59
+ import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
60
+ import type { PipelineStep } from "../../pipeline/pipeline.js";
61
+ import type { RequestContext } from "../../pipeline/request-context.js";
62
+ import type {
63
+ ReferenceParent,
64
+ SpecReference,
65
+ } from "../../pipeline/steps/references.js";
66
+ import { checkReferences } from "../../pipeline/steps/references.js";
67
+ import { metadataOf } from "../../pipeline/steps/shapes.js";
68
+ import { unmarshalTaskConfig } from "./converter/unmarshal.js";
69
+ import { nestedTasks } from "./validation/task-config-constraints.js";
70
+
71
+ /** The one sentence for an `agent` string that is none of the three forms. */
72
+ export function malformedAgentReferenceMessage(
73
+ taskName: string,
74
+ agent: string,
75
+ ): string {
76
+ return `task '${taskName}': agent '${agent}' is not a reference; write 'slug' for an agent of this organization or 'org/slug' for another organization's.`;
77
+ }
78
+
79
+ /** What an `agent` string is before the rule reads it (the module header). */
80
+ export type AgentReferenceForm =
81
+ | { readonly kind: "literal"; readonly org: string; readonly slug: string }
82
+ | { readonly kind: "relative"; readonly slug: string }
83
+ | { readonly kind: "run-time" }
84
+ | { readonly kind: "malformed" };
85
+
86
+ /** Opens every runtime expression and placeholder the runner evaluates; never part of a slug. */
87
+ const RUNTIME_EXPRESSION_MARKER = "${";
88
+
89
+ export function classifyAgentReference(agent: string): AgentReferenceForm {
90
+ if (agent.includes(RUNTIME_EXPRESSION_MARKER)) {
91
+ return { kind: "run-time" };
92
+ }
93
+ const parts = agent.split("/");
94
+ if (parts.length === 1 && parts[0] !== "") {
95
+ return { kind: "relative", slug: parts[0]! };
96
+ }
97
+ if (parts.length === 2 && parts[0] !== "" && parts[1] !== "") {
98
+ return { kind: "literal", org: parts[0]!, slug: parts[1]! };
99
+ }
100
+ return { kind: "malformed" };
101
+ }
102
+
103
+ /**
104
+ * Every `agent_call` task's agent the write can judge, as a reference,
105
+ * nested tasks included in declaration order: a literal as written, a
106
+ * relative slug in `parentOrg` and marked relative, a value fixed only at
107
+ * run not at all. Throws INVALID_ARGUMENT on the first malformed string. A
108
+ * config that does not decode is skipped: the spec validation step has
109
+ * already refused the workflow for it, and this collector never
110
+ * double-reports.
111
+ */
112
+ export function collectAgentCallReferences(
113
+ spec: WorkflowSpec | undefined,
114
+ parentOrg: string,
115
+ ): SpecReference[] {
116
+ const refs: SpecReference[] = [];
117
+ if (spec === undefined) {
118
+ return refs;
119
+ }
120
+ const visit = (task: WorkflowTask): void => {
121
+ if (task.taskConfig !== undefined) {
122
+ let config;
123
+ try {
124
+ config = unmarshalTaskConfig(task.kind, task.taskConfig);
125
+ } catch {
126
+ config = undefined;
127
+ }
128
+ if (config !== undefined) {
129
+ if (task.kind === WorkflowTaskKind.agent_call) {
130
+ const agent = (config as AgentCallTaskConfig).agent;
131
+ const form = classifyAgentReference(agent);
132
+ switch (form.kind) {
133
+ case "literal":
134
+ refs.push({
135
+ kind: ApiResourceKind.agent,
136
+ org: form.org,
137
+ slug: form.slug,
138
+ });
139
+ break;
140
+ case "relative":
141
+ refs.push({
142
+ kind: ApiResourceKind.agent,
143
+ org: parentOrg,
144
+ slug: form.slug,
145
+ resolvesIn: "running-organization",
146
+ });
147
+ break;
148
+ case "run-time":
149
+ break;
150
+ case "malformed":
151
+ throw invalidArgumentError(
152
+ malformedAgentReferenceMessage(task.name, agent),
153
+ );
154
+ default: {
155
+ const exhaustive: never = form;
156
+ throw new Error(
157
+ `unknown agent reference form: ${JSON.stringify(exhaustive)}`,
158
+ );
159
+ }
160
+ }
161
+ }
162
+ for (const nested of nestedTasks(config)) {
163
+ visit(nested);
164
+ }
165
+ }
166
+ }
167
+ for (const compensate of task.compensate) {
168
+ visit(compensate);
169
+ }
170
+ };
171
+ for (const task of spec.tasks) {
172
+ visit(task);
173
+ }
174
+ return refs;
175
+ }
176
+
177
+ export function newValidateAgentCallReferencesStep(
178
+ store: Store,
179
+ ): PipelineStep<typeof WorkflowSchema> {
180
+ return {
181
+ name: "ValidateAgentCallReferences",
182
+ async execute(ctx: RequestContext<typeof WorkflowSchema>): Promise<void> {
183
+ const metadata = metadataOf(ctx.newState);
184
+ if (metadata === undefined) {
185
+ throw internalError(
186
+ new Error("resource metadata is nil"),
187
+ "validate agent_call references",
188
+ );
189
+ }
190
+ const refs = collectAgentCallReferences(ctx.newState.spec, metadata.org);
191
+ if (refs.length === 0) {
192
+ return;
193
+ }
194
+ const parent: ReferenceParent = {
195
+ org: metadata.org,
196
+ visibility: metadata.visibility,
197
+ };
198
+ const refusal = await checkReferences(store, parent, refs);
199
+ if (refusal !== undefined) {
200
+ throw refusal;
201
+ }
202
+ },
203
+ };
204
+ }
@@ -78,10 +78,6 @@ import {
78
78
  } from "../../pipeline/steps/authorize-resolved-target.js";
79
79
  import { versionHistoryTarget } from "../../pipeline/steps/version-history.js";
80
80
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
81
- import {
82
- newAuthorizeVisibilityTransitionStep,
83
- newGuardPublicVisibilityStep,
84
- } from "../../pipeline/steps/visibility-gates.js";
85
81
  import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
86
82
  import {
87
83
  setAuditFieldsForUpdate,
@@ -112,7 +108,16 @@ import {
112
108
  TARGET_RESOURCE_KEY,
113
109
  newLoadTargetStep,
114
110
  } from "../../pipeline/steps/load-target.js";
115
- import { newNormalizeReferencesStep } from "../../pipeline/steps/references.js";
111
+ import {
112
+ collectSpecReferences,
113
+ newGuardReferenceFloorOnEscalationStep,
114
+ newNormalizeReferencesStep,
115
+ newValidateReferencesStep,
116
+ } from "../../pipeline/steps/references.js";
117
+ import {
118
+ collectAgentCallReferences,
119
+ newValidateAgentCallReferencesStep,
120
+ } from "./agent-call-references.js";
116
121
  import {
117
122
  newCleanupIamPoliciesStep,
118
123
  newCreateAuthorizationTuplesStep,
@@ -238,9 +243,10 @@ async function createWorkflow(
238
243
  .addStep(newResolveSlugStep())
239
244
  .addStep(newCheckDuplicateStep(deps.store))
240
245
  .addStep(newBuildNewStateStep())
241
- .addStep(newGuardPublicVisibilityStep(deps.authorizer))
242
246
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
243
247
  .addStep(newNormalizeReferencesStep())
248
+ .addStep(newValidateReferencesStep(deps.store))
249
+ .addStep(newValidateAgentCallReferencesStep(deps.store))
244
250
  .addStep(newPopulateServerlessValidationStep(deps.logger))
245
251
  .addStep(newComputeVersionHashStep(deps.logger))
246
252
  .addStep(newPopulateVersionHashStep(true))
@@ -305,6 +311,8 @@ async function update(
305
311
  .addStep(newBuildUpdateStateStep())
306
312
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
307
313
  .addStep(newNormalizeReferencesStep())
314
+ .addStep(newValidateReferencesStep(deps.store))
315
+ .addStep(newValidateAgentCallReferencesStep(deps.store))
308
316
  .addStep(newPopulateServerlessValidationStepForUpdate(deps.logger))
309
317
  .addStep(newComputeVersionHashStep(deps.logger))
310
318
  .addStep(newCheckVersionChangedStep(deps.logger))
@@ -411,8 +419,8 @@ async function deleteWorkflow(
411
419
  // updateVisibility — update_visibility.go: a targeted metadata update (only
412
420
  // metadata.visibility changes; spec/status untouched). The level check runs
413
421
  // AFTER load, preserving the cross-edition error precedence: unknown id +
414
- // bad level = NOT_FOUND on both editions. CW-9's suite block is the wire
415
- // pin (clientPublicVisibilityWrites, true on local targets).
422
+ // bad level = NOT_FOUND on both editions. The workflow conformance suite's
423
+ // visibility block is the wire pin.
416
424
  // ---------------------------------------------------------------------------
417
425
 
418
426
  const UPDATE_VISIBILITY_WORKFLOW_KEY = "updateVisibilityWorkflow";
@@ -452,10 +460,20 @@ async function updateVisibility(
452
460
  newRecordVisibilityBeforeUpdateStep(UPDATE_VISIBILITY_WORKFLOW_KEY),
453
461
  )
454
462
  .addStep(newValidateVisibilityUpdateStep())
463
+ // The reference floor's second door: a workflow may not be raised above
464
+ // the agents its agent_call tasks run.
455
465
  .addStep(
456
- newAuthorizeVisibilityTransitionStep(
466
+ newGuardReferenceFloorOnEscalationStep(
467
+ deps.store,
457
468
  UPDATE_VISIBILITY_WORKFLOW_KEY,
458
- deps.authorizer,
469
+ [
470
+ (row) => collectSpecReferences(WorkflowSchema, row),
471
+ (row) =>
472
+ collectAgentCallReferences(
473
+ (row as Workflow).spec,
474
+ (row as Workflow).metadata?.org ?? "",
475
+ ),
476
+ ],
459
477
  ),
460
478
  )
461
479
  .addStep(newSetWorkflowVisibilityStep())