@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.
- package/dist/authorization/evaluator.d.ts +7 -1
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +19 -11
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +2 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +0 -1
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +0 -2
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +0 -6
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +2 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +3 -17
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agent/steps.d.ts +0 -1
- package/dist/domain/agent/steps.d.ts.map +1 -1
- package/dist/domain/agent/steps.js +1 -40
- package/dist/domain/agent/steps.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +6 -1
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentchannel/steps.d.ts +19 -0
- package/dist/domain/agentchannel/steps.d.ts.map +1 -1
- package/dist/domain/agentchannel/steps.js +32 -0
- package/dist/domain/agentchannel/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -9
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
- package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
- package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +16 -31
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +36 -102
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +7 -3
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
- package/dist/domain/agentexecution/run-target.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +5 -5
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +6 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +43 -8
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +71 -8
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +7 -1
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +58 -0
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +109 -4
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/artifact/controller.js +21 -25
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js +2 -0
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +2 -0
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/environment/personal.d.ts +42 -0
- package/dist/domain/environment/personal.d.ts.map +1 -0
- package/dist/domain/environment/personal.js +96 -0
- package/dist/domain/environment/personal.js.map +1 -0
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +2 -0
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/executioncontext/steps.d.ts.map +1 -1
- package/dist/domain/executioncontext/steps.js +2 -1
- package/dist/domain/executioncontext/steps.js.map +1 -1
- package/dist/domain/mcpserver/connect.d.ts +0 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +24 -79
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +2 -0
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +8 -7
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +6 -1
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +23 -0
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +35 -0
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/oauthapp/controller.d.ts.map +1 -1
- package/dist/domain/oauthapp/controller.js +2 -0
- package/dist/domain/oauthapp/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +5 -24
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +2 -2
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +7 -1
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/schedule/steps.d.ts +19 -0
- package/dist/domain/schedule/steps.d.ts.map +1 -1
- package/dist/domain/schedule/steps.js +32 -0
- package/dist/domain/schedule/steps.js.map +1 -1
- package/dist/domain/session/controller.d.ts +0 -7
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +11 -16
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +9 -8
- package/dist/domain/session/run-target.d.ts.map +1 -1
- package/dist/domain/session/run-target.js.map +1 -1
- package/dist/domain/session/steps.d.ts +8 -36
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +27 -172
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +8 -17
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +30 -107
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/version-resolution.d.ts +5 -0
- package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
- package/dist/domain/workflow/version-resolution.js +113 -0
- package/dist/domain/workflow/version-resolution.js.map +1 -0
- package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
- package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/workflowexecution/create-steps.js +4 -3
- package/dist/domain/workflowexecution/create-steps.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +6 -1
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +20 -0
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +39 -0
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +19 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +22 -3
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +41 -7
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +13 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +2 -2
- package/dist/pipeline/apiresource-labels.js +2 -2
- package/dist/pipeline/errors.d.ts +2 -2
- package/dist/pipeline/errors.js +2 -2
- package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
- package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
- package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
- package/dist/pipeline/steps/authorize.d.ts +27 -0
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +49 -10
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +2 -1
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +12 -12
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts +9 -5
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js +11 -15
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/version-history.d.ts +11 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -1
- package/dist/pipeline/steps/version-history.js +26 -0
- package/dist/pipeline/steps/version-history.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
- package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
- package/src/authorization/evaluator.ts +7 -0
- package/src/authorization/list-read-scope.ts +22 -11
- package/src/authorization/model/__tests__/registry.test.ts +2 -0
- package/src/authorization/model/organization.ts +2 -0
- package/src/boot/compose.ts +0 -1
- package/src/boot/inprocess.ts +0 -9
- package/src/domain/agent/__tests__/agent.test.ts +0 -68
- package/src/domain/agent/controller.ts +11 -36
- package/src/domain/agent/steps.ts +1 -60
- package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/agentchannel/controller.ts +19 -0
- package/src/domain/agentchannel/steps.ts +40 -0
- package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
- package/src/domain/agentexecution/controller.ts +6 -10
- package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
- package/src/domain/agentexecution/create-steps.ts +38 -138
- package/src/domain/agentexecution/run-target.ts +7 -3
- package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
- package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
- package/src/domain/agentinstance/controller.ts +24 -5
- package/src/domain/agentinstance/steps.ts +82 -8
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
- package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
- package/src/domain/agentshare/controller.ts +21 -0
- package/src/domain/agentshare/steps.ts +181 -15
- package/src/domain/artifact/controller.ts +18 -22
- package/src/domain/channelapp/controller.ts +10 -0
- package/src/domain/environment/__tests__/personal.test.ts +143 -0
- package/src/domain/environment/controller.ts +10 -0
- package/src/domain/environment/personal.ts +141 -0
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
- package/src/domain/executioncontext/controller.ts +10 -0
- package/src/domain/executioncontext/steps.ts +2 -1
- package/src/domain/mcpserver/connect.ts +32 -92
- package/src/domain/mcpserver/controller.ts +13 -1
- package/src/domain/mcpserver/start-connect.ts +17 -11
- package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
- package/src/domain/memory/controller.ts +11 -0
- package/src/domain/memory/steps.ts +41 -0
- package/src/domain/oauthapp/controller.ts +10 -0
- package/src/domain/plugin/controller.ts +22 -36
- package/src/domain/plugin/overlay/sanitize.ts +2 -2
- package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/schedule/controller.ts +20 -0
- package/src/domain/schedule/steps.ts +45 -2
- package/src/domain/session/__tests__/run-gate.test.ts +19 -52
- package/src/domain/session/__tests__/session.test.ts +27 -203
- package/src/domain/session/controller.ts +10 -30
- package/src/domain/session/run-target.ts +9 -8
- package/src/domain/session/steps.ts +33 -267
- package/src/domain/skill/controller.ts +24 -23
- package/src/domain/workflow/controller.ts +58 -145
- package/src/domain/workflow/version-resolution.ts +166 -0
- package/src/domain/workflowexecution/create-steps.ts +4 -3
- package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
- package/src/domain/workflowinstance/controller.ts +19 -0
- package/src/domain/workflowinstance/steps.ts +49 -1
- package/src/extensions/__tests__/composed-support.ts +38 -4
- package/src/extensions/__tests__/identity.test.ts +44 -1
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
- package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
- package/src/extensions/identity.ts +23 -5
- package/src/extensions/list-read-scope.ts +47 -7
- package/src/pipeline/apiresource-labels.ts +2 -2
- package/src/pipeline/errors.ts +2 -2
- package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
- package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
- package/src/pipeline/steps/authorize.ts +70 -13
- package/src/pipeline/steps/guard-memory-capture.ts +2 -1
- package/src/pipeline/steps/guard-reserved-labels.ts +12 -14
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/version-history.ts +34 -0
- package/dist/domain/agent/defaultagent.d.ts +0 -37
- package/dist/domain/agent/defaultagent.d.ts.map +0 -1
- package/dist/domain/agent/defaultagent.js +0 -104
- package/dist/domain/agent/defaultagent.js.map +0 -1
- 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 —
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
)
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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:
|
|
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
|