@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
@@ -45,7 +45,6 @@ import {
45
45
  ApiResourceKind,
46
46
  ApiResourceKindSchema,
47
47
  } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
48
- import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
49
48
  import type {
50
49
  ApiResourceReference,
51
50
  UpdateVisibilityInput,
@@ -71,9 +70,13 @@ import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
71
70
  import { RequestContext } from "../../pipeline/request-context.js";
72
71
  import {
73
72
  authorizeDirect,
74
- authorizeResolvedResource,
75
73
  newAuthorizeStep,
76
74
  } from "../../pipeline/steps/authorize.js";
75
+ import {
76
+ loadedTargetAsMethod,
77
+ newAuthorizeResolvedTargetStep,
78
+ } from "../../pipeline/steps/authorize-resolved-target.js";
79
+ import { versionHistoryTarget } from "../../pipeline/steps/version-history.js";
77
80
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
78
81
  import {
79
82
  newAuthorizeVisibilityTransitionStep,
@@ -90,10 +93,7 @@ import {
90
93
  newExtractResourceIdStep,
91
94
  newLoadExistingForDeleteStep,
92
95
  } from "../../pipeline/steps/delete.js";
93
- import {
94
- findResourceBySlug,
95
- requireOrgForReference,
96
- } from "../../pipeline/steps/helpers.js";
96
+ import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
97
97
  import {
98
98
  newDeleteSearchIndexStep,
99
99
  newIndexSearchStep,
@@ -150,6 +150,7 @@ import {
150
150
  newValidateWorkflowSpecStep,
151
151
  truncateHash,
152
152
  } from "./steps.js";
153
+ import { newLoadWorkflowByReferenceStep } from "./version-resolution.js";
153
154
  import type { WorkflowInstanceCreatorProvider } from "./steps.js";
154
155
 
155
156
  export interface WorkflowControllerDeps {
@@ -192,7 +193,7 @@ export function registerWorkflowServices(
192
193
  });
193
194
  router.service(WorkflowQueryController, {
194
195
  get: (id, ctx) => get(deps, id, ctx),
195
- getByReference: (ref) => getByReference(deps, ref),
196
+ getByReference: (ref, ctx) => getByReference(deps, ref, ctx),
196
197
  listVersions: (input, ctx) => listVersions(deps, input, ctx),
197
198
  getVersion: (input, ctx) => getVersion(deps, input, callerIdentityOf(ctx)),
198
199
  });
@@ -831,130 +832,47 @@ async function get(
831
832
  return reqCtx.get(TARGET_RESOURCE_KEY) as Workflow;
832
833
  }
833
834
 
834
- /** A 64-hex value is an exact content hash; anything else is a tag. */
835
- const WORKFLOW_HASH_PATTERN = /^[a-f0-9]{64}$/;
836
-
837
835
  /**
838
- * getByReference — query.go: slug/org resolution plus version resolution.
839
- * Empty/"latest" → the current head; a version matching the head's hash or
840
- * tag → the head; otherwise the audit store by hash or tag, with the
841
- * snapshot's tag overlaid from the audit column (the source of truth) so
842
- * callers never see a stale embedded tag after a tag move. Like
843
- * validateSpec, this branching read flow reads more honestly written
844
- * directly than as a pipeline (Go's own note).
836
+ * getByReference — the slug/org read plus the version ladder, as a chain:
837
+ * Authorize (a no-op under the lane's skip annotation) → ValidateProto →
838
+ * LoadWorkflowByReference (the ladder, version-resolution.ts) →
839
+ * AuthorizeResolvedTarget, which asks of the loaded head's id exactly what
840
+ * `get` asks by id, so a slug reveals nothing a member could not read
841
+ * through the id. The archived snapshot shares the head's id, so the check
842
+ * on a versioned read is the check on the head.
845
843
  */
846
844
  async function getByReference(
847
845
  deps: WorkflowControllerDeps,
848
846
  ref: ApiResourceReference,
847
+ ctx: HandlerContext,
849
848
  ): Promise<Workflow> {
850
- if (ref.slug === "") {
851
- throw invalidArgumentError("slug is required in reference");
852
- }
853
-
854
- // Workflow is org-scoped: its slug is unique only within an org, so an
855
- // empty-org reference is under-specified.
856
- requireOrgForReference(ApiResourceKind.workflow, ref.org);
857
-
858
- if (
859
- ref.kind !== ApiResourceKind.api_resource_kind_unknown &&
860
- ref.kind !== ApiResourceKind.workflow
861
- ) {
862
- // Go renders both sides with .String(): the NAME for defined values and
863
- // the bare NUMBER for unknown ones (proto3 open enums preserve them, and
864
- // ref.kind carries no defined_only rule) — enumToJson would throw on the
865
- // unknown arm (panel finding).
866
- const got = ApiResourceKind[ref.kind] ?? String(ref.kind);
867
- throw invalidArgumentError(
868
- `kind mismatch: expected ${ApiResourceKind[ApiResourceKind.workflow]}, got ${got}`,
869
- );
870
- }
871
-
872
- let mainWorkflow: Workflow | undefined;
873
- try {
874
- mainWorkflow = await findResourceBySlug(
875
- deps.store,
876
- ApiResourceKind.workflow,
877
- WorkflowSchema,
878
- ref.slug,
879
- ref.org,
880
- );
881
- } catch (error) {
882
- // Go wraps the slug scan's store failure (query.go:110) — without this
883
- // the raw storage error would cross the wire as Unknown (panel finding).
884
- throw internalError(error, "failed to list workflows");
885
- }
886
- if (mainWorkflow === undefined) {
887
- throw notFoundError("workflow", ref.slug);
888
- }
889
-
890
- const version = ref.version.trim();
891
- if (version === "" || version === "latest") {
892
- return mainWorkflow;
893
- }
894
-
895
- if (workflowMatchesVersion(mainWorkflow, version)) {
896
- return mainWorkflow;
897
- }
898
-
899
- const archived = await findAuditWorkflowByVersion(
900
- deps.store,
901
- mainWorkflow.metadata!.id,
902
- version,
849
+ const reqCtx = new RequestContext(
850
+ WorkflowQueryController.method.getByReference.input,
851
+ ref,
852
+ callerIdentityOf(ctx),
853
+ kindOf(ctx),
903
854
  );
904
- if (archived === undefined) {
905
- throw notFoundError("workflow version", `${ref.slug}:${version}`);
906
- }
907
- return archived;
908
- }
909
-
910
- function workflowMatchesVersion(wf: Workflow, version: string): boolean {
911
- if (wf.status === undefined) {
912
- return false;
913
- }
914
- if (WORKFLOW_HASH_PATTERN.test(version)) {
915
- return wf.status.versionHash === version;
916
- }
917
- return wf.metadata?.version?.tag === version;
918
- }
919
-
920
- async function findAuditWorkflowByVersion(
921
- store: Store,
922
- workflowId: string,
923
- version: string,
924
- ): Promise<Workflow | undefined> {
925
- let rec: AuditRecord;
926
- try {
927
- rec = WORKFLOW_HASH_PATTERN.test(version)
928
- ? await store.getAuditRecordByHash(
929
- ApiResourceKind.workflow,
930
- workflowId,
931
- version,
932
- )
933
- : await store.getAuditRecordByTag(
934
- ApiResourceKind.workflow,
935
- workflowId,
936
- version,
937
- );
938
- } catch (error) {
939
- if (error instanceof AuditNotFoundError) {
940
- return undefined;
941
- }
942
- throw internalError(error, "failed to query workflow audit by version");
943
- }
944
-
945
- let wf: Workflow;
946
- try {
947
- wf = fromBinary(WorkflowSchema, rec.data);
948
- } catch (error) {
949
- throw internalError(error, "failed to decode archived workflow version");
950
- }
951
- // Overlay the authoritative tag (audit column) onto the snapshot's
952
- // metadata.version.tag — the snapshot's embedded tag is only correct as
953
- // of archival time.
954
- wf.metadata ??= create(ApiResourceMetadataSchema);
955
- wf.metadata.version ??= create(ApiResourceMetadataVersionSchema);
956
- wf.metadata.version.tag = rec.tag;
957
- return wf;
855
+ await newPipeline<typeof WorkflowQueryController.method.getByReference.input>(
856
+ "workflow-get-by-reference",
857
+ deps.logger,
858
+ )
859
+ .addStep(
860
+ newAuthorizeStep(
861
+ WorkflowQueryController.method.getByReference,
862
+ deps.authorizer,
863
+ ),
864
+ )
865
+ .addStep(newValidateProtoStep())
866
+ .addStep(newLoadWorkflowByReferenceStep(deps.store))
867
+ .addStep(
868
+ newAuthorizeResolvedTargetStep(
869
+ deps.authorizer,
870
+ loadedTargetAsMethod(WorkflowQueryController.method.get),
871
+ ),
872
+ )
873
+ .build()
874
+ .execute(reqCtx);
875
+ return reqCtx.get(TARGET_RESOURCE_KEY) as Workflow;
958
876
  }
959
877
 
960
878
  // ---------------------------------------------------------------------------
@@ -996,28 +914,23 @@ async function listVersions(
996
914
  )
997
915
  .addStep(newValidateProtoStep())
998
916
  .addStep(newResolveWorkflowBySlugStep(deps.store))
999
- .addStep({
1000
- name: "AuthorizeResolvedWorkflow",
1001
- async execute(stepCtx): Promise<void> {
1002
- // The Java WorkflowListVersionsHandler's mid-chain can_view on the
1003
- // RESOLVED workflow id (20260830.01 ruling Q8 — the DD-007 pattern
1004
- // "the two traced ListVersions handlers port onto"; the skip
1005
- // annotation exists because the target resolves from org+slug, not
1006
- // a request field). Deny copy is the Java handler's byte-pinned
1007
- // error_msg. The resolve step above already answered NOT_FOUND for
1008
- // an unknown slug, the load-first order both editions share.
1009
- await authorizeResolvedResource(
1010
- deps.authorizer,
1011
- stepCtx.callerIdentity,
1012
- {
1013
- permission: IamPermission.can_view,
1014
- resourceKind: ApiResourceKind.workflow,
1015
- resourceId: stepCtx.get(LIST_VERSIONS_WORKFLOW_ID_KEY) as string,
1016
- },
917
+ // The Java WorkflowListVersionsHandler's mid-chain can_view on the
918
+ // RESOLVED workflow id (the skip annotation exists because the target
919
+ // resolves from org+slug, not a request field); the copy is the Java
920
+ // handler's byte-pinned error_msg, and the resolve step above already
921
+ // answered NOT_FOUND for an unknown slug, the load-first order both
922
+ // editions share.
923
+ .addStep(
924
+ newAuthorizeResolvedTargetStep(
925
+ deps.authorizer,
926
+ versionHistoryTarget(
927
+ ApiResourceKind.workflow,
1017
928
  "unauthorized to view workflow version history",
1018
- );
1019
- },
1020
- })
929
+ LIST_VERSIONS_WORKFLOW_ID_KEY,
930
+ ),
931
+ "AuthorizeResolvedWorkflow",
932
+ ),
933
+ )
1021
934
  .addStep(newLoadAndMapWorkflowVersionsStep(deps.store))
1022
935
  .build()
1023
936
  .execute(reqCtx);
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Workflow version resolution — the getByReference ladder as a pipeline
3
+ * step, so the lane can carry the resolved-target authorization the
4
+ * position-1 annotation cannot express (the target is a slug, not a field).
5
+ *
6
+ * The ladder is the Go query.go read, moved here from the direct handler
7
+ * without a behaviour change: slug required; org required (a workflow's slug
8
+ * is unique only within an org); kind mismatch refused with Go's rendering
9
+ * (the NAME for defined values, the bare NUMBER for unknown ones, because
10
+ * ref.kind carries no defined_only rule and enumToJson would throw on the
11
+ * unknown arm); the slug scan's store failure wrapped so a raw storage error
12
+ * never crosses the wire as Unknown; empty or "latest" is the current head;
13
+ * a version matching the head's hash or tag is the head; otherwise the
14
+ * audit store by hash or tag, with the snapshot's tag overlaid from the
15
+ * audit column (the source of truth) so callers never see a stale embedded
16
+ * tag after a tag move. The resolved row lands under TARGET_RESOURCE_KEY,
17
+ * the loader contract every getByReference chain shares.
18
+ *
19
+ * Deliberately NOT the shared ladder in pipeline/steps/version-history.ts:
20
+ * that one decodes through the typed audit reads and carries no tag
21
+ * overlay, so folding the two would change what one of them returns for a
22
+ * moved tag. Consolidating them is a separate change with its own proof.
23
+ *
24
+ * Proven by __tests__/workflow.test.ts's getByReference block and the
25
+ * conformance suite's workflow reference arms.
26
+ */
27
+ import { create, fromBinary } from "@bufbuild/protobuf";
28
+
29
+ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
30
+ import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
31
+ import {
32
+ ApiResourceMetadataSchema,
33
+ ApiResourceMetadataVersionSchema,
34
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
35
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
36
+
37
+ import {
38
+ internalError,
39
+ invalidArgumentError,
40
+ notFoundError,
41
+ } from "../../pipeline/errors.js";
42
+ import type { PipelineStep } from "../../pipeline/pipeline.js";
43
+ import type { RequestContext } from "../../pipeline/request-context.js";
44
+ import type { GetByReferenceDesc } from "../../pipeline/steps/authorize-resolved-target.js";
45
+ import {
46
+ findResourceBySlug,
47
+ requireOrgForReference,
48
+ } from "../../pipeline/steps/helpers.js";
49
+ import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
50
+ import { AuditNotFoundError } from "../../store/interface.js";
51
+ import type { AuditRecord, Store } from "../../store/interface.js";
52
+
53
+ /** A 64-hex value is an exact content hash; anything else is a tag. */
54
+ const WORKFLOW_HASH_PATTERN = /^[a-f0-9]{64}$/;
55
+
56
+ export function newLoadWorkflowByReferenceStep(
57
+ store: Store,
58
+ ): PipelineStep<GetByReferenceDesc> {
59
+ return {
60
+ name: "LoadWorkflowByReference",
61
+ async execute(ctx: RequestContext<GetByReferenceDesc>): Promise<void> {
62
+ const ref = ctx.input;
63
+ if (ref.slug === "") {
64
+ throw invalidArgumentError("slug is required in reference");
65
+ }
66
+
67
+ requireOrgForReference(ApiResourceKind.workflow, ref.org);
68
+
69
+ if (
70
+ ref.kind !== ApiResourceKind.api_resource_kind_unknown &&
71
+ ref.kind !== ApiResourceKind.workflow
72
+ ) {
73
+ const got = ApiResourceKind[ref.kind] ?? String(ref.kind);
74
+ throw invalidArgumentError(
75
+ `kind mismatch: expected ${ApiResourceKind[ApiResourceKind.workflow]}, got ${got}`,
76
+ );
77
+ }
78
+
79
+ let mainWorkflow: Workflow | undefined;
80
+ try {
81
+ mainWorkflow = await findResourceBySlug(
82
+ store,
83
+ ApiResourceKind.workflow,
84
+ WorkflowSchema,
85
+ ref.slug,
86
+ ref.org,
87
+ );
88
+ } catch (error) {
89
+ throw internalError(error, "failed to list workflows");
90
+ }
91
+ if (mainWorkflow === undefined) {
92
+ throw notFoundError("workflow", ref.slug);
93
+ }
94
+
95
+ const version = ref.version.trim();
96
+ if (
97
+ version === "" ||
98
+ version === "latest" ||
99
+ workflowMatchesVersion(mainWorkflow, version)
100
+ ) {
101
+ ctx.set(TARGET_RESOURCE_KEY, mainWorkflow);
102
+ return;
103
+ }
104
+
105
+ const archived = await findAuditWorkflowByVersion(
106
+ store,
107
+ mainWorkflow.metadata!.id,
108
+ version,
109
+ );
110
+ if (archived === undefined) {
111
+ throw notFoundError("workflow version", `${ref.slug}:${version}`);
112
+ }
113
+ ctx.set(TARGET_RESOURCE_KEY, archived);
114
+ },
115
+ };
116
+ }
117
+
118
+ function workflowMatchesVersion(wf: Workflow, version: string): boolean {
119
+ if (wf.status === undefined) {
120
+ return false;
121
+ }
122
+ if (WORKFLOW_HASH_PATTERN.test(version)) {
123
+ return wf.status.versionHash === version;
124
+ }
125
+ return wf.metadata?.version?.tag === version;
126
+ }
127
+
128
+ async function findAuditWorkflowByVersion(
129
+ store: Store,
130
+ workflowId: string,
131
+ version: string,
132
+ ): Promise<Workflow | undefined> {
133
+ let rec: AuditRecord;
134
+ try {
135
+ rec = WORKFLOW_HASH_PATTERN.test(version)
136
+ ? await store.getAuditRecordByHash(
137
+ ApiResourceKind.workflow,
138
+ workflowId,
139
+ version,
140
+ )
141
+ : await store.getAuditRecordByTag(
142
+ ApiResourceKind.workflow,
143
+ workflowId,
144
+ version,
145
+ );
146
+ } catch (error) {
147
+ if (error instanceof AuditNotFoundError) {
148
+ return undefined;
149
+ }
150
+ throw internalError(error, "failed to query workflow audit by version");
151
+ }
152
+
153
+ let wf: Workflow;
154
+ try {
155
+ wf = fromBinary(WorkflowSchema, rec.data);
156
+ } catch (error) {
157
+ throw internalError(error, "failed to decode archived workflow version");
158
+ }
159
+ // Overlay the authoritative tag (audit column) onto the snapshot's
160
+ // metadata.version.tag — the snapshot's embedded tag is only correct as
161
+ // of archival time.
162
+ wf.metadata ??= create(ApiResourceMetadataSchema);
163
+ wf.metadata.version ??= create(ApiResourceMetadataVersionSchema);
164
+ wf.metadata.version.tag = rec.tag;
165
+ return wf;
166
+ }
@@ -62,9 +62,10 @@ export type ExecutionWorkflowInstanceCreatorProvider =
62
62
  /**
63
63
  * ValidateWorkflowOrInstance — at least one of workflow_id /
64
64
  * workflow_instance_id must be provided. Matches the cloud
65
- * WorkflowExecutionCreateHandler. AgentExecution differs: it has a
66
- * ResolveDefaultAgent step, so its guard is an unreachable invariant
67
- * (Internal), not reachable InvalidArgument validation (issue #196).
65
+ * WorkflowExecutionCreateHandler. AgentExecution differs on purpose: its
66
+ * all-empty shape is legal (the built-in assistant), so it has no such
67
+ * step; a workflow execution has no built-in workflow to fall back to
68
+ * (issue #196).
68
69
  */
69
70
  export function newValidateWorkflowOrInstanceStep(): PipelineStep<ExecutionDesc> {
70
71
  return {
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Pins the create lane's authorization question (steps.ts,
3
+ * resolveWorkflowInstanceCreateTargets): a personal instance asks
4
+ * can_execute on the parent workflow with the Java handler's copy; a
5
+ * default instance the server composed in-process for the parent's
6
+ * organization asks nothing; the same label from the wire or for another
7
+ * organization takes the bar; a missing parent throws.
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { create } from "@bufbuild/protobuf";
11
+
12
+ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
13
+ import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
14
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
15
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
16
+
17
+ import type { CallerIdentity } from "../../../extensions/identity.js";
18
+ import {
19
+ DEFAULT_INSTANCE_LABEL,
20
+ RESERVED_LABEL_TRUE,
21
+ } from "../../../pipeline/apiresource-labels.js";
22
+ import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
23
+ import { RequestContext } from "../../../pipeline/request-context.js";
24
+ import {
25
+ PARENT_WORKFLOW_KEY,
26
+ WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE,
27
+ resolveWorkflowInstanceCreateTargets,
28
+ } from "../steps.js";
29
+
30
+ const parent = create(WorkflowSchema, {
31
+ metadata: { id: "wf_01parent", name: "nightly", org: "acme" },
32
+ });
33
+
34
+ function ctxFor(
35
+ org: string,
36
+ caller: CallerIdentity,
37
+ labels: Record<string, string> = {},
38
+ stashParent = true,
39
+ ) {
40
+ const ctx = new RequestContext(
41
+ WorkflowInstanceSchema,
42
+ create(WorkflowInstanceSchema, {
43
+ metadata: { name: "mine", org, labels },
44
+ spec: { workflowId: parent.metadata!.id },
45
+ }),
46
+ caller,
47
+ ApiResourceKind.workflow_instance,
48
+ );
49
+ if (stashParent) {
50
+ ctx.set(PARENT_WORKFLOW_KEY, parent);
51
+ }
52
+ return ctx;
53
+ }
54
+
55
+ const DEFAULT_LABELS = { [DEFAULT_INSTANCE_LABEL]: RESERVED_LABEL_TRUE };
56
+
57
+ describe("resolveWorkflowInstanceCreateTargets", () => {
58
+ it("a personal instance asks can_execute on the parent workflow", () => {
59
+ expect(
60
+ resolveWorkflowInstanceCreateTargets(
61
+ ctxFor("acme", testCallerIdentity()),
62
+ ),
63
+ ).toEqual([
64
+ {
65
+ permission: IamPermission.can_execute,
66
+ resourceKind: ApiResourceKind.workflow,
67
+ resourceId: "wf_01parent",
68
+ deniedMessage: WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE,
69
+ },
70
+ ]);
71
+ });
72
+
73
+ it("a default instance the server composed in-process for the parent's organization asks nothing", () => {
74
+ expect(
75
+ resolveWorkflowInstanceCreateTargets(
76
+ ctxFor(
77
+ "acme",
78
+ testCallerIdentity({ callerClass: "user", origin: "in-process" }),
79
+ DEFAULT_LABELS,
80
+ ),
81
+ ),
82
+ ).toEqual([]);
83
+ });
84
+
85
+ it("the label from the wire, or for another organization, takes the bar", () => {
86
+ expect(
87
+ resolveWorkflowInstanceCreateTargets(
88
+ ctxFor("acme", testCallerIdentity(), DEFAULT_LABELS),
89
+ ),
90
+ ).toHaveLength(1);
91
+ expect(
92
+ resolveWorkflowInstanceCreateTargets(
93
+ ctxFor(
94
+ "other-org",
95
+ testCallerIdentity({ callerClass: "user", origin: "in-process" }),
96
+ DEFAULT_LABELS,
97
+ ),
98
+ ),
99
+ ).toHaveLength(1);
100
+ });
101
+
102
+ it("a missing parent is a broken chain invariant and throws", () => {
103
+ expect(() =>
104
+ resolveWorkflowInstanceCreateTargets(
105
+ ctxFor("acme", testCallerIdentity(), {}, false),
106
+ ),
107
+ ).toThrow("parent workflow not found in context");
108
+ });
109
+ });
@@ -53,6 +53,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
53
53
  import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
54
54
  import { RequestContext } from "../../pipeline/request-context.js";
55
55
  import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
56
+ import {
57
+ loadedTargetAsMethod,
58
+ newAuthorizeResolvedTargetStep,
59
+ } from "../../pipeline/steps/authorize-resolved-target.js";
56
60
  import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
57
61
  import {
58
62
  newAuthorizeVisibilityTransitionStep,
@@ -108,6 +112,7 @@ import {
108
112
  newRejectDefaultWorkflowInstanceVisibilityUpdateStep,
109
113
  newValidateInstanceUpdateStep,
110
114
  newValidateSameOrgBusinessRuleStep,
115
+ resolveWorkflowInstanceCreateTargets,
111
116
  } from "./steps.js";
112
117
  import type { ParentWorkflowLoaderProvider } from "./steps.js";
113
118
 
@@ -182,6 +187,14 @@ async function createInstance(
182
187
  .addStep(newValidateVisibilityStep())
183
188
  .addStep(newResolveSlugStep())
184
189
  .addStep(newLoadParentWorkflowStep(deps.parentWorkflowLoader, deps.logger))
190
+ // Before the same-org rule, whose refusal names the parent's organization
191
+ // (steps.ts, resolveWorkflowInstanceCreateTargets).
192
+ .addStep(
193
+ newAuthorizeResolvedTargetStep(
194
+ deps.authorizer,
195
+ resolveWorkflowInstanceCreateTargets,
196
+ ),
197
+ )
185
198
  .addStep(newValidateSameOrgBusinessRuleStep(deps.logger))
186
199
  .addStep(newCheckDuplicateStep(deps.store))
187
200
  .addStep(newBuildNewStateStep())
@@ -707,6 +720,12 @@ async function getByReference(
707
720
  )
708
721
  .addStep(newValidateProtoStep())
709
722
  .addStep(newLoadByReferenceStep(deps.store, WorkflowInstanceSchema))
723
+ .addStep(
724
+ newAuthorizeResolvedTargetStep(
725
+ deps.authorizer,
726
+ loadedTargetAsMethod(WorkflowInstanceQueryController.method.get),
727
+ ),
728
+ )
710
729
  .build()
711
730
  .execute(reqCtx);
712
731
  return reqCtx.get(TARGET_RESOURCE_KEY) as WorkflowInstance;
@@ -16,8 +16,10 @@ import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/ap
16
16
  import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
17
17
  import type { WorkflowInstance } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
18
18
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
19
+ import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
19
20
 
20
21
  import type { Logger } from "../../boot/logger.js";
22
+ import { isServerComposedRequest } from "../../extensions/identity.js";
21
23
  import { isDefaultInstance } from "../../pipeline/apiresource-labels.js";
22
24
  import {
23
25
  failedPreconditionError,
@@ -27,6 +29,7 @@ import {
27
29
  } from "../../pipeline/errors.js";
28
30
  import type { PipelineStep } from "../../pipeline/pipeline.js";
29
31
  import type { RequestContext } from "../../pipeline/request-context.js";
32
+ import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
30
33
  import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
31
34
  import { rejectDefaultInstanceVisibilityUpdate } from "../../pipeline/steps/validate-visibility.js";
32
35
  import { ResourceNotFoundError } from "../../store/interface.js";
@@ -72,7 +75,8 @@ export function newLoadParentWorkflowStep(
72
75
  parent = await loaderProvider().get(workflowId);
73
76
  } catch (error) {
74
77
  logger.warn("Parent workflow not found", {
75
- error: error instanceof ConnectError ? error.rawMessage : String(error),
78
+ error:
79
+ error instanceof ConnectError ? error.rawMessage : String(error),
76
80
  workflowId,
77
81
  });
78
82
  throw notFoundError("Workflow", workflowId);
@@ -83,6 +87,50 @@ export function newLoadParentWorkflowStep(
83
87
  };
84
88
  }
85
89
 
90
+ /** The deny copy of the parent's bar, the Java WorkflowInstanceCreateHandler's. */
91
+ export const WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE =
92
+ "You don't have permission to create instances of this workflow";
93
+
94
+ /**
95
+ * The create lane's authorization question, for AuthorizeResolvedTarget
96
+ * after LoadParentWorkflow and BEFORE ValidateSameOrgBusinessRule (whose
97
+ * refusal names the parent's organization, which a refused caller should
98
+ * not learn). The RPC is is_skip_authorization because its target is the
99
+ * PARENT, loaded from spec.workflow_id, not a request field.
100
+ *
101
+ * One question: may the caller run this workflow (can_execute on the
102
+ * parent, the relation the run gate asks; the same-org rule already binds
103
+ * the instance's organization to the workflow's, so there is no separate
104
+ * organization bar). A DEFAULT instance asks nothing, for the reason its
105
+ * agent twin gives (agentinstance/steps.ts): it is the workflow's row,
106
+ * composed by the server for the run's human, keyed on the in-process
107
+ * origin, the reserved label and the parent's organization together.
108
+ */
109
+ export function resolveWorkflowInstanceCreateTargets(
110
+ ctx: RequestContext<InstanceDesc>,
111
+ ): ReadonlyArray<AuthorizationTarget> {
112
+ const parent = ctx.get(PARENT_WORKFLOW_KEY) as Workflow | undefined;
113
+ if (parent === undefined) {
114
+ throw new Error("parent workflow not found in context");
115
+ }
116
+ const metadata = ctx.newState.metadata;
117
+ if (
118
+ isDefaultInstance(metadata) &&
119
+ isServerComposedRequest(ctx.callerIdentity) &&
120
+ (metadata?.org ?? "") === (parent.metadata?.org ?? "")
121
+ ) {
122
+ return [];
123
+ }
124
+ return [
125
+ {
126
+ permission: IamPermission.can_execute,
127
+ resourceKind: ApiResourceKind.workflow,
128
+ resourceId: parent.metadata?.id ?? "",
129
+ deniedMessage: WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE,
130
+ },
131
+ ];
132
+ }
133
+
86
134
  /**
87
135
  * ValidateSameOrgBusinessRule — create.go: instances must live in the same
88
136
  * organization as the parent workflow; cross-org instances could leak