@stigmer/server 3.23.0 → 3.23.1-dev.20260923075205

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 (267) 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 +54 -0
  100. package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
  101. package/dist/domain/workflow/agent-call-references.js +97 -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 +1 -1
  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-meta.d.ts +12 -3
  126. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  127. package/dist/pipeline/apiresource-meta.js +21 -10
  128. package/dist/pipeline/apiresource-meta.js.map +1 -1
  129. package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
  130. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  131. package/dist/pipeline/steps/authorization-tuples.js +9 -11
  132. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  133. package/dist/pipeline/steps/references.d.ts +178 -20
  134. package/dist/pipeline/steps/references.d.ts.map +1 -1
  135. package/dist/pipeline/steps/references.js +344 -43
  136. package/dist/pipeline/steps/references.js.map +1 -1
  137. package/dist/query/search/controller.js +0 -1
  138. package/dist/query/search/controller.js.map +1 -1
  139. package/dist/query/search/criteria.d.ts +1 -5
  140. package/dist/query/search/criteria.d.ts.map +1 -1
  141. package/dist/query/search/criteria.js +3 -13
  142. package/dist/query/search/criteria.js.map +1 -1
  143. package/dist/query/search/handler.d.ts +5 -4
  144. package/dist/query/search/handler.d.ts.map +1 -1
  145. package/dist/query/search/handler.js +7 -6
  146. package/dist/query/search/handler.js.map +1 -1
  147. package/dist/query/search/query-store.d.ts.map +1 -1
  148. package/dist/query/search/query-store.js +0 -2
  149. package/dist/query/search/query-store.js.map +1 -1
  150. package/dist/store/interface.d.ts +3 -6
  151. package/dist/store/interface.d.ts.map +1 -1
  152. package/dist/store/interface.js.map +1 -1
  153. package/dist/store/postgres/migrations.d.ts +3 -1
  154. package/dist/store/postgres/migrations.d.ts.map +1 -1
  155. package/dist/store/postgres/migrations.js +35 -1
  156. package/dist/store/postgres/migrations.js.map +1 -1
  157. package/dist/store/postgres/store.d.ts.map +1 -1
  158. package/dist/store/postgres/store.js +4 -9
  159. package/dist/store/postgres/store.js.map +1 -1
  160. package/dist/store/public-visibility-retired.d.ts +67 -0
  161. package/dist/store/public-visibility-retired.d.ts.map +1 -0
  162. package/dist/store/public-visibility-retired.js +55 -0
  163. package/dist/store/public-visibility-retired.js.map +1 -0
  164. package/dist/store/sqlite/migrations.d.ts +3 -1
  165. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  166. package/dist/store/sqlite/migrations.js +36 -1
  167. package/dist/store/sqlite/migrations.js.map +1 -1
  168. package/dist/store/sqlite/store.d.ts.map +1 -1
  169. package/dist/store/sqlite/store.js +4 -8
  170. package/dist/store/sqlite/store.js.map +1 -1
  171. package/package.json +6 -6
  172. package/src/authorization/README.md +24 -26
  173. package/src/authorization/__tests__/authorizer.test.ts +10 -9
  174. package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
  175. package/src/authorization/__tests__/evaluator.test.ts +14 -73
  176. package/src/authorization/__tests__/facts.test.ts +2 -2
  177. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +19 -27
  178. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
  179. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
  180. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +1 -15
  181. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
  182. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
  183. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
  184. package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
  185. package/src/authorization/__tests__/store-tests.test.ts +2 -6
  186. package/src/authorization/__tests__/tuples.test.ts +12 -11
  187. package/src/authorization/authorizer.ts +4 -9
  188. package/src/authorization/derived-tuples.ts +14 -31
  189. package/src/authorization/evaluator.ts +8 -44
  190. package/src/authorization/list-read-scope.ts +5 -11
  191. package/src/authorization/model/agent.ts +2 -5
  192. package/src/authorization/model/agent_instance.ts +0 -2
  193. package/src/authorization/model/mcp_server.ts +0 -2
  194. package/src/authorization/model/plugin.ts +1 -4
  195. package/src/authorization/model/rewrite.ts +8 -13
  196. package/src/authorization/model/skill.ts +0 -2
  197. package/src/authorization/model/workflow.ts +0 -2
  198. package/src/authorization/model/workflow_instance.ts +0 -2
  199. package/src/authorization/organization-directory.ts +1 -3
  200. package/src/authorization/store-test-kit.ts +16 -75
  201. package/src/authorization/tuples.ts +16 -39
  202. package/src/domain/agent/__tests__/agent.test.ts +11 -4
  203. package/src/domain/agent/controller.ts +7 -7
  204. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
  205. package/src/domain/agentchannel/controller.ts +6 -1
  206. package/src/domain/agentexecution/controller.ts +6 -1
  207. package/src/domain/agentinstance/controller.ts +6 -12
  208. package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
  209. package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
  210. package/src/domain/agentshare/constants.ts +14 -26
  211. package/src/domain/agentshare/controller.ts +6 -1
  212. package/src/domain/agentshare/steps.ts +41 -194
  213. package/src/domain/environment/controller.ts +5 -8
  214. package/src/domain/executioncontext/controller.ts +5 -1
  215. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
  216. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
  217. package/src/domain/mcpserver/controller.ts +6 -12
  218. package/src/domain/plugin/controller.ts +1 -8
  219. package/src/domain/plugin/push.ts +8 -16
  220. package/src/domain/schedule/controller.ts +6 -1
  221. package/src/domain/session/controller.ts +12 -4
  222. package/src/domain/skill/controller.ts +0 -7
  223. package/src/domain/workflow/__tests__/agent-call-references.test.ts +222 -0
  224. package/src/domain/workflow/agent-call-references.ts +157 -0
  225. package/src/domain/workflow/controller.ts +28 -10
  226. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
  227. package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
  228. package/src/domain/workflowexecution/controller.ts +7 -2
  229. package/src/domain/workflowinstance/controller.ts +6 -12
  230. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
  231. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  232. package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
  233. package/src/extensions/authorization-queries.ts +6 -5
  234. package/src/extensions/list-read-scope.ts +1 -1
  235. package/src/extensions/resource-authorization.ts +3 -5
  236. package/src/index.ts +0 -1
  237. package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
  238. package/src/pipeline/apiresource-meta.ts +21 -10
  239. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
  240. package/src/pipeline/steps/__tests__/references.test.ts +703 -0
  241. package/src/pipeline/steps/authorization-tuples.ts +9 -11
  242. package/src/pipeline/steps/references.ts +578 -70
  243. package/src/query/search/__tests__/criteria.test.ts +0 -4
  244. package/src/query/search/__tests__/query-store.test.ts +11 -40
  245. package/src/query/search/controller.ts +0 -1
  246. package/src/query/search/criteria.ts +0 -14
  247. package/src/query/search/handler.ts +6 -7
  248. package/src/query/search/query-store.ts +0 -2
  249. package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
  250. package/src/store/__tests__/store-contract.ts +6 -51
  251. package/src/store/interface.ts +3 -6
  252. package/src/store/postgres/__tests__/migrations.test.ts +187 -6
  253. package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
  254. package/src/store/postgres/migrations.ts +48 -1
  255. package/src/store/postgres/store.ts +4 -11
  256. package/src/store/public-visibility-retired.ts +119 -0
  257. package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
  258. package/src/store/sqlite/migrations.ts +45 -1
  259. package/src/store/sqlite/store.ts +4 -10
  260. package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
  261. package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
  262. package/dist/pipeline/steps/visibility-gates.js +0 -139
  263. package/dist/pipeline/steps/visibility-gates.js.map +0 -1
  264. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
  265. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
  266. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
  267. package/src/pipeline/steps/visibility-gates.ts +0 -174
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Pins the workflow domain's collector for the reference rule
3
+ * (../agent-call-references.ts): the strict `slug` / `org/slug` parser and
4
+ * its refusal copy, the walk over top-level, nested and compensate tasks
5
+ * in declaration order with the bare form resolved to the workflow's
6
+ * organization, a config that does not decode skipped rather than
7
+ * double-reported, and the step over a real store answering the rule's
8
+ * verdicts — a missing agent with the rule's copy, another organization's
9
+ * org-visible agent with the one cross-organization sentence, a
10
+ * platform-visible one admitted.
11
+ */
12
+ import { create } from "@bufbuild/protobuf";
13
+ import { Code, ConnectError } from "@connectrpc/connect";
14
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
15
+
16
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
17
+ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
18
+ import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
19
+ import type { WorkflowSpec } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
20
+ import { WorkflowSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
21
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
22
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
23
+
24
+ import { RequestContext } from "../../../pipeline/request-context.js";
25
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
26
+ import {
27
+ missingReferencesMessage,
28
+ notAvailableReferenceMessage,
29
+ referenceTargetKind,
30
+ } from "../../../pipeline/steps/references.js";
31
+ import type { SqliteStore } from "../../../store/sqlite/store.js";
32
+ import { tempStore } from "../../../store/sqlite/__tests__/support.js";
33
+ import {
34
+ collectAgentCallReferences,
35
+ malformedAgentReferenceMessage,
36
+ newValidateAgentCallReferencesStep,
37
+ parseAgentReference,
38
+ } from "../agent-call-references.js";
39
+
40
+ const V = ApiResourceVisibility;
41
+ const AGENT = referenceTargetKind(ApiResourceKind.agent)!;
42
+
43
+ function agentCall(name: string, agent: string) {
44
+ return {
45
+ name,
46
+ kind: WorkflowTaskKind.agent_call,
47
+ taskConfig: { agent, message: "do the thing" },
48
+ };
49
+ }
50
+
51
+ function specOf(tasks: ReadonlyArray<Record<string, unknown>>): WorkflowSpec {
52
+ return create(WorkflowSpecSchema, { tasks } as never);
53
+ }
54
+
55
+ describe("parseAgentReference", () => {
56
+ it("accepts `slug` and `org/slug` and nothing else", () => {
57
+ expect(parseAgentReference("reviewer")).toEqual({
58
+ org: "",
59
+ slug: "reviewer",
60
+ });
61
+ expect(parseAgentReference("acme/reviewer")).toEqual({
62
+ org: "acme",
63
+ slug: "reviewer",
64
+ });
65
+ for (const malformed of ["", "/reviewer", "acme/", "a/b/c", "//"]) {
66
+ expect(parseAgentReference(malformed), malformed).toBeUndefined();
67
+ }
68
+ });
69
+ });
70
+
71
+ describe("collectAgentCallReferences", () => {
72
+ it("collects top-level, nested and compensate agent_call tasks in declaration order, the bare form resolved to the workflow's organization", () => {
73
+ const spec = specOf([
74
+ agentCall("first", "reviewer"),
75
+ {
76
+ name: "loop",
77
+ kind: WorkflowTaskKind.for_each,
78
+ taskConfig: {
79
+ each: "item",
80
+ in: "${ .items }",
81
+ // A nested task inside the Struct is JSON: proto field names.
82
+ do: [
83
+ {
84
+ name: "inner",
85
+ kind: "agent_call",
86
+ task_config: { agent: "globex/summarizer", message: "sum" },
87
+ },
88
+ ],
89
+ },
90
+ compensate: [agentCall("undo", "cleaner")],
91
+ },
92
+ {
93
+ name: "pause",
94
+ kind: WorkflowTaskKind.wait,
95
+ taskConfig: { duration: {} },
96
+ },
97
+ ]);
98
+ expect(collectAgentCallReferences(spec, "acme")).toEqual([
99
+ { kind: ApiResourceKind.agent, org: "acme", slug: "reviewer" },
100
+ { kind: ApiResourceKind.agent, org: "globex", slug: "summarizer" },
101
+ { kind: ApiResourceKind.agent, org: "acme", slug: "cleaner" },
102
+ ]);
103
+ });
104
+
105
+ it("refuses a malformed agent string naming the task", () => {
106
+ const spec = specOf([agentCall("broken", "a/b/c")]);
107
+ expect(() => collectAgentCallReferences(spec, "acme")).toThrow(
108
+ malformedAgentReferenceMessage("broken", "a/b/c"),
109
+ );
110
+ let error: unknown;
111
+ try {
112
+ collectAgentCallReferences(spec, "acme");
113
+ } catch (e) {
114
+ error = e;
115
+ }
116
+ expect((error as ConnectError).code).toBe(Code.InvalidArgument);
117
+ });
118
+
119
+ it("skips a config that does not decode — the spec validation step has already refused it", () => {
120
+ const spec = specOf([
121
+ {
122
+ name: "odd",
123
+ kind: WorkflowTaskKind.agent_call,
124
+ taskConfig: { agent: "reviewer", not_a_field: true },
125
+ },
126
+ ]);
127
+ expect(collectAgentCallReferences(spec, "acme")).toEqual([]);
128
+ expect(collectAgentCallReferences(undefined, "acme")).toEqual([]);
129
+ });
130
+ });
131
+
132
+ describe("ValidateAgentCallReferences over a store", () => {
133
+ let store: SqliteStore;
134
+ let cleanup: () => Promise<void>;
135
+
136
+ beforeEach(() => {
137
+ const temp = tempStore();
138
+ store = temp.store;
139
+ cleanup = temp.cleanup;
140
+ });
141
+
142
+ afterEach(async () => {
143
+ await cleanup();
144
+ });
145
+
146
+ async function seedAgent(
147
+ id: string,
148
+ org: string,
149
+ slug: string,
150
+ visibility: ApiResourceVisibility,
151
+ ): Promise<void> {
152
+ await store.saveResource(
153
+ ApiResourceKind.agent,
154
+ id,
155
+ AgentSchema,
156
+ create(AgentSchema, {
157
+ apiVersion: "agentic.stigmer.ai/v1",
158
+ kind: "Agent",
159
+ metadata: { id, name: slug, slug, org, visibility },
160
+ spec: { instructions: "a conformant instruction body" },
161
+ }),
162
+ );
163
+ }
164
+
165
+ function workflowCalling(...agents: string[]) {
166
+ return create(WorkflowSchema, {
167
+ apiVersion: "agentic.stigmer.ai/v1",
168
+ kind: "Workflow",
169
+ metadata: { name: "Pipeline", org: "acme", visibility: V.visibility_org },
170
+ spec: specOf(agents.map((agent, i) => agentCall(`call_${i}`, agent))),
171
+ });
172
+ }
173
+
174
+ async function run(workflow: ReturnType<typeof workflowCalling>) {
175
+ const ctx = new RequestContext(
176
+ WorkflowSchema,
177
+ workflow,
178
+ testCallerIdentity(),
179
+ ApiResourceKind.workflow,
180
+ );
181
+ try {
182
+ await newValidateAgentCallReferencesStep(store).execute(ctx);
183
+ return undefined;
184
+ } catch (error) {
185
+ return error;
186
+ }
187
+ }
188
+
189
+ it("a missing agent is refused with the rule's copy; an existing one passes", async () => {
190
+ const error = await run(workflowCalling("reviewer"));
191
+ expect(error).toBeInstanceOf(ConnectError);
192
+ expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
193
+ expect((error as ConnectError).rawMessage).toBe(
194
+ missingReferencesMessage(AGENT, [{ slug: "reviewer", org: "acme" }]),
195
+ );
196
+ await seedAgent("agt_1", "acme", "reviewer", V.visibility_org);
197
+ expect(await run(workflowCalling("reviewer"))).toBeUndefined();
198
+ });
199
+
200
+ it("another organization's agent is admitted only at platform visibility, with the one sentence otherwise", async () => {
201
+ await seedAgent("agt_shared", "globex", "shared", V.visibility_platform);
202
+ await seedAgent("agt_internal", "globex", "internal", V.visibility_org);
203
+ expect(await run(workflowCalling("globex/shared"))).toBeUndefined();
204
+ const error = await run(workflowCalling("globex/internal"));
205
+ expect((error as ConnectError).rawMessage).toBe(
206
+ notAvailableReferenceMessage(AGENT, {
207
+ kind: ApiResourceKind.agent,
208
+ org: "globex",
209
+ slug: "internal",
210
+ }),
211
+ );
212
+ });
213
+
214
+ it("the floor holds for the agent a workflow calls: an org-visible workflow may not call a private agent", async () => {
215
+ await seedAgent("agt_mine", "acme", "mine", V.visibility_private);
216
+ const error = await run(workflowCalling("mine"));
217
+ expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
218
+ expect((error as ConnectError).rawMessage).toContain(
219
+ "referenced agent 'acme/mine' is visibility_private while this resource is visibility_org",
220
+ );
221
+ });
222
+ });
@@ -0,0 +1,157 @@
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, `"slug"` or `"org/slug"`, inside a `google.protobuf.Struct`
5
+ * config the generic spec walker cannot see, so the rule would never meet
6
+ * them. This step parses each task's config through the converter's own
7
+ * strict decoder (`unmarshalTaskConfig`, the one reader of a task config),
8
+ * walks nested control-flow tasks exactly as the constraint validator does,
9
+ * turns every `agent` string into a reference with the workflow as parent,
10
+ * and asks the rule's one function — same clauses, same copy. It lives in
11
+ * the workflow domain and not inside `InProcessValidator` because the
12
+ * validator is pure over the spec by design and a rule that reads the
13
+ * target's row cannot be.
14
+ *
15
+ * The parser here is STRICT: exactly `slug` or `org/slug`, both segments
16
+ * non-empty, anything else refused at write naming the task. Two lenient
17
+ * readers of the same string exist downstream and disagree with each other
18
+ * (the runner's `parseAgentReference` in activities/call-agent.ts splits on
19
+ * the first `/` and drops a third segment; the CLI's `parseReference` in
20
+ * client-apps/cli/src/resources/reference.ts refuses a leading slash);
21
+ * refusing every malformed shape here means neither ever meets one. The
22
+ * three cannot share code — the packages share only the protos — so the
23
+ * execution conformance suite is where their agreement is proven.
24
+ *
25
+ * Runs after NormalizeReferences and ValidateReferences on the workflow
26
+ * create and update chains, so the org the walk sees is the workflow's own
27
+ * for a bare slug. Proven by __tests__/agent-call-references.test.ts and
28
+ * the workflow conformance suite's reference arm.
29
+ */
30
+ import type { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
31
+ import type { AgentCallTaskConfig } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/tasks/agent_call_pb";
32
+ import type {
33
+ WorkflowSpec,
34
+ WorkflowTask,
35
+ } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
36
+ import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
37
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
38
+
39
+ import type { Store } from "../../store/interface.js";
40
+ import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
41
+ import type { PipelineStep } from "../../pipeline/pipeline.js";
42
+ import type { RequestContext } from "../../pipeline/request-context.js";
43
+ import type {
44
+ ReferenceParent,
45
+ SpecReference,
46
+ } from "../../pipeline/steps/references.js";
47
+ import { checkReferences } from "../../pipeline/steps/references.js";
48
+ import { metadataOf } from "../../pipeline/steps/shapes.js";
49
+ import { unmarshalTaskConfig } from "./converter/unmarshal.js";
50
+ import { nestedTasks } from "./validation/task-config-constraints.js";
51
+
52
+ /** The one sentence for an `agent` string that is neither `slug` nor `org/slug`. */
53
+ export function malformedAgentReferenceMessage(
54
+ taskName: string,
55
+ agent: string,
56
+ ): string {
57
+ return `task '${taskName}': agent '${agent}' is not a reference; write 'slug' for an agent of this organization or 'org/slug' for another organization's.`;
58
+ }
59
+
60
+ /**
61
+ * `slug` or `org/slug` → the reference, with `org` empty for the bare form
62
+ * (the caller fills the parent's). Returns undefined for any other shape.
63
+ */
64
+ export function parseAgentReference(
65
+ agent: string,
66
+ ): { readonly org: string; readonly slug: string } | undefined {
67
+ const parts = agent.split("/");
68
+ if (parts.length === 1) {
69
+ return parts[0] === "" ? undefined : { org: "", slug: parts[0]! };
70
+ }
71
+ if (parts.length === 2 && parts[0] !== "" && parts[1] !== "") {
72
+ return { org: parts[0]!, slug: parts[1]! };
73
+ }
74
+ return undefined;
75
+ }
76
+
77
+ /**
78
+ * Every `agent_call` task's agent as a reference, nested tasks included in
79
+ * declaration order, the bare form resolved to `parentOrg`. Throws
80
+ * INVALID_ARGUMENT on the first malformed string. A config that does not
81
+ * decode is skipped: the spec validation step has already refused the
82
+ * workflow for it, and this collector never double-reports.
83
+ */
84
+ export function collectAgentCallReferences(
85
+ spec: WorkflowSpec | undefined,
86
+ parentOrg: string,
87
+ ): SpecReference[] {
88
+ const refs: SpecReference[] = [];
89
+ if (spec === undefined) {
90
+ return refs;
91
+ }
92
+ const visit = (task: WorkflowTask): void => {
93
+ if (task.taskConfig !== undefined) {
94
+ let config;
95
+ try {
96
+ config = unmarshalTaskConfig(task.kind, task.taskConfig);
97
+ } catch {
98
+ config = undefined;
99
+ }
100
+ if (config !== undefined) {
101
+ if (task.kind === WorkflowTaskKind.agent_call) {
102
+ const agent = (config as AgentCallTaskConfig).agent;
103
+ const parsed = parseAgentReference(agent);
104
+ if (parsed === undefined) {
105
+ throw invalidArgumentError(
106
+ malformedAgentReferenceMessage(task.name, agent),
107
+ );
108
+ }
109
+ refs.push({
110
+ kind: ApiResourceKind.agent,
111
+ org: parsed.org === "" ? parentOrg : parsed.org,
112
+ slug: parsed.slug,
113
+ });
114
+ }
115
+ for (const nested of nestedTasks(config)) {
116
+ visit(nested);
117
+ }
118
+ }
119
+ }
120
+ for (const compensate of task.compensate) {
121
+ visit(compensate);
122
+ }
123
+ };
124
+ for (const task of spec.tasks) {
125
+ visit(task);
126
+ }
127
+ return refs;
128
+ }
129
+
130
+ export function newValidateAgentCallReferencesStep(
131
+ store: Store,
132
+ ): PipelineStep<typeof WorkflowSchema> {
133
+ return {
134
+ name: "ValidateAgentCallReferences",
135
+ async execute(ctx: RequestContext<typeof WorkflowSchema>): Promise<void> {
136
+ const metadata = metadataOf(ctx.newState);
137
+ if (metadata === undefined) {
138
+ throw internalError(
139
+ new Error("resource metadata is nil"),
140
+ "validate agent_call references",
141
+ );
142
+ }
143
+ const refs = collectAgentCallReferences(ctx.newState.spec, metadata.org);
144
+ if (refs.length === 0) {
145
+ return;
146
+ }
147
+ const parent: ReferenceParent = {
148
+ org: metadata.org,
149
+ visibility: metadata.visibility,
150
+ };
151
+ const refusal = await checkReferences(store, parent, refs);
152
+ if (refusal !== undefined) {
153
+ throw refusal;
154
+ }
155
+ },
156
+ };
157
+ }
@@ -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())
@@ -1525,7 +1525,7 @@
1525
1525
  "type": "string"
1526
1526
  },
1527
1527
  "org": {
1528
- "description": "Organization that owns the referenced resource. When non-empty: must be a valid org slug (lowercase alphanumeric with hyphens, starts with a letter, 1-63 characters). Example: \"stigmer\", \"acme-corp\". When empty: the reference is relative — the server resolves it to the parent resource's organization at write time. All stored and returned references always have org populated (absolute form). Use empty org for same-org references (the common case). Use explicit org for cross-org references (e.g., marketplace resources).",
1528
+ "description": "Organization that owns the referenced resource. When non-empty: must be a valid org slug (lowercase alphanumeric with hyphens, starts with a letter, 1-63 characters). Example: \"stigmer\", \"acme-corp\". When empty: the reference is relative — the server resolves it to the parent resource's organization at write time. All stored and returned references always have org populated (absolute form). Use empty org for same-org references (the common case). An explicit other org is accepted only when that organization is a platform that shares the resource with yours (visibility_platform).",
1529
1529
  "maxLength": 63,
1530
1530
  "pattern": "^$|^[a-z][a-z0-9-]*[a-z0-9]$",
1531
1531
  "type": "string"
@@ -105,7 +105,10 @@ function taskConstraintViolations(task: WorkflowTask): string[] {
105
105
  * task_config is a Struct again — that is what the walker's recursion is
106
106
  * for).
107
107
  */
108
- function configConstraintViolations(task: WorkflowTask, cfg: Message): string[] {
108
+ function configConstraintViolations(
109
+ task: WorkflowTask,
110
+ cfg: Message,
111
+ ): string[] {
109
112
  const schema = taskConfigSchemaFor(task.kind);
110
113
  if (schema === undefined) {
111
114
  // Unknown kinds were already rejected by validateTaskKinds; an
@@ -129,9 +132,10 @@ function configConstraintViolations(task: WorkflowTask, cfg: Message): string[]
129
132
  * The WorkflowTask lists embedded in a control-flow task config, in
130
133
  * declaration order — the same recursion set the converter walks
131
134
  * (convertForTask/convertForkTask/convertTryTask) and the cloud Java
132
- * TaskConfigStrictParser mirrors.
135
+ * TaskConfigStrictParser mirrors. Exported for the write-time reference
136
+ * collector (../agent-call-references.ts), which walks the same tree.
133
137
  */
134
- function nestedTasks(cfg: Message): WorkflowTask[] {
138
+ export function nestedTasks(cfg: Message): WorkflowTask[] {
135
139
  switch (cfg.$typeName) {
136
140
  case ForTaskConfigSchema.typeName:
137
141
  return (cfg as ForTaskConfig).do;
@@ -72,7 +72,10 @@ import {
72
72
  TARGET_RESOURCE_KEY,
73
73
  newLoadTargetStep,
74
74
  } from "../../pipeline/steps/load-target.js";
75
- import { newNormalizeReferencesStep } from "../../pipeline/steps/references.js";
75
+ import {
76
+ newNormalizeReferencesStep,
77
+ newValidateReferencesStep,
78
+ } from "../../pipeline/steps/references.js";
76
79
  import {
77
80
  newCleanupIamPoliciesStep,
78
81
  newCreateAuthorizationTuplesStep,
@@ -299,7 +302,8 @@ async function createExecution(
299
302
  .addStep(newCheckDuplicateStep(deps.store))
300
303
  .addStep(newBuildNewStateStep())
301
304
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
302
- .addStep(newNormalizeReferencesStep());
305
+ .addStep(newNormalizeReferencesStep())
306
+ .addStep(newValidateReferencesStep(deps.store));
303
307
  // The ratified sandbox-acquisition gate slot (blueprint 03 §3a; C4):
304
308
  // the Java-verified capacity-gate position — after Authorize and every
305
309
  // resolution step (the default instance, like Java's, side-effects
@@ -389,6 +393,7 @@ async function update(
389
393
  .addStep(newBuildUpdateStateStep())
390
394
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
391
395
  .addStep(newNormalizeReferencesStep())
396
+ .addStep(newValidateReferencesStep(deps.store))
392
397
  .addStep(newPersistStep(deps.store))
393
398
  .addStep(
394
399
  newIndexSearchStep(
@@ -58,10 +58,6 @@ import {
58
58
  newAuthorizeResolvedTargetStep,
59
59
  } from "../../pipeline/steps/authorize-resolved-target.js";
60
60
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
61
- import {
62
- newAuthorizeVisibilityTransitionStep,
63
- newGuardPublicVisibilityStep,
64
- } from "../../pipeline/steps/visibility-gates.js";
65
61
  import {
66
62
  newBuildNewStateStep,
67
63
  setAuditFieldsForUpdate,
@@ -91,7 +87,10 @@ import {
91
87
  TARGET_RESOURCE_KEY,
92
88
  newLoadTargetStep,
93
89
  } from "../../pipeline/steps/load-target.js";
94
- import { newNormalizeReferencesStep } from "../../pipeline/steps/references.js";
90
+ import {
91
+ newNormalizeReferencesStep,
92
+ newValidateReferencesStep,
93
+ } from "../../pipeline/steps/references.js";
95
94
  import {
96
95
  newCleanupIamPoliciesStep,
97
96
  newCreateAuthorizationTuplesStep,
@@ -198,9 +197,9 @@ async function createInstance(
198
197
  .addStep(newValidateSameOrgBusinessRuleStep(deps.logger))
199
198
  .addStep(newCheckDuplicateStep(deps.store))
200
199
  .addStep(newBuildNewStateStep())
201
- .addStep(newGuardPublicVisibilityStep(deps.authorizer))
202
200
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
203
201
  .addStep(newNormalizeReferencesStep())
202
+ .addStep(newValidateReferencesStep(deps.store))
204
203
  .addStep(newPersistStep(deps.store))
205
204
  .addStep(
206
205
  newCreateAuthorizationTuplesStep(
@@ -249,6 +248,7 @@ async function update(
249
248
  .addStep(newBuildUpdateStateStep())
250
249
  .addStep(newGuardReservedLabelsStep(deps.authorizer))
251
250
  .addStep(newNormalizeReferencesStep())
251
+ .addStep(newValidateReferencesStep(deps.store))
252
252
  .addStep(newPersistStep(deps.store))
253
253
  .addStep(
254
254
  newIndexSearchStep(
@@ -407,12 +407,6 @@ async function updateVisibility(
407
407
  ),
408
408
  )
409
409
  .addStep(newValidateVisibilityUpdateStep())
410
- .addStep(
411
- newAuthorizeVisibilityTransitionStep(
412
- UPDATE_VISIBILITY_INSTANCE_KEY,
413
- deps.authorizer,
414
- ),
415
- )
416
410
  .addStep(newSetInstanceVisibilityStep())
417
411
  .addStep(
418
412
  newPersistInstanceStep(