@stigmer/server 3.21.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/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +2 -0
- package/dist/domain/agent/controller.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/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/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.map +1 -1
- package/dist/domain/mcpserver/connect.js +8 -7
- 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/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/steps.d.ts +8 -9
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +17 -21
- 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/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/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 +6 -7
- 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/domain/agent/controller.ts +10 -0
- 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/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/controller.ts +10 -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 +11 -10
- 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/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/steps.ts +24 -32
- 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/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/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 +6 -9
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/version-history.ts +34 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins AuthorizeMemberAudience (steps.ts), the member-profile lane's gate:
|
|
3
|
+
* membership (can_view on the reference's organization) is asked before
|
|
4
|
+
* the share is loaded; deny and not-found both answer the lane's one
|
|
5
|
+
* NOT_FOUND, byte-identical to a missing share, so a share URL teaches a
|
|
6
|
+
* non-member nothing; unavailable is INTERNAL; an empty org asks nothing
|
|
7
|
+
* and is left to the loader's INVALID_ARGUMENT; the internal class skips.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { create } from "@bufbuild/protobuf";
|
|
11
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
12
|
+
|
|
13
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
import { ApiResourceReferenceSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
15
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
Authorizer,
|
|
19
|
+
AuthzCheck,
|
|
20
|
+
AuthzDecision,
|
|
21
|
+
} from "../../../extensions/authorizer.js";
|
|
22
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
23
|
+
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
24
|
+
import { RequestContext } from "../../../pipeline/request-context.js";
|
|
25
|
+
import { AUTHORIZATION_UNAVAILABLE_MESSAGE } from "../../../pipeline/steps/authorize.js";
|
|
26
|
+
import { newAuthorizeMemberAudienceStep, sharedNotFound } from "../steps.js";
|
|
27
|
+
|
|
28
|
+
function fakeAuthorizer(decision: AuthzDecision): {
|
|
29
|
+
authorizer: Authorizer;
|
|
30
|
+
checks: AuthzCheck[];
|
|
31
|
+
} {
|
|
32
|
+
const checks: AuthzCheck[] = [];
|
|
33
|
+
return {
|
|
34
|
+
checks,
|
|
35
|
+
authorizer: {
|
|
36
|
+
authorize(_caller, check) {
|
|
37
|
+
checks.push(check);
|
|
38
|
+
return Promise.resolve(decision);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ctxFor(org: string, caller: CallerIdentity = testCallerIdentity()) {
|
|
45
|
+
return new RequestContext(
|
|
46
|
+
ApiResourceReferenceSchema,
|
|
47
|
+
create(ApiResourceReferenceSchema, { org, slug: "helper" }),
|
|
48
|
+
caller,
|
|
49
|
+
ApiResourceKind.agent_share,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function captureError(
|
|
54
|
+
run: () => void | Promise<void>,
|
|
55
|
+
): Promise<ConnectError> {
|
|
56
|
+
try {
|
|
57
|
+
await run();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return ConnectError.from(error);
|
|
60
|
+
}
|
|
61
|
+
throw new Error("expected the step to reject");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("AuthorizeMemberAudience", () => {
|
|
65
|
+
it("asks can_view on the reference's organization and admits an allow", async () => {
|
|
66
|
+
const { authorizer, checks } = fakeAuthorizer({ kind: "allow" });
|
|
67
|
+
await newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme"));
|
|
68
|
+
expect(checks).toEqual([
|
|
69
|
+
{
|
|
70
|
+
permission: IamPermission.can_view,
|
|
71
|
+
resourceKind: ApiResourceKind.organization,
|
|
72
|
+
resourceId: "acme",
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("deny and not-found both answer the lane's NOT_FOUND, byte-identical to a missing share", async () => {
|
|
78
|
+
for (const decision of [
|
|
79
|
+
{ kind: "deny", reason: "not a member" },
|
|
80
|
+
{ kind: "not-found" },
|
|
81
|
+
] as const) {
|
|
82
|
+
const { authorizer } = fakeAuthorizer(decision);
|
|
83
|
+
const error = await captureError(() =>
|
|
84
|
+
newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme")),
|
|
85
|
+
);
|
|
86
|
+
expect(error.code).toBe(Code.NotFound);
|
|
87
|
+
expect(error.rawMessage).toBe(sharedNotFound("helper").rawMessage);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("unavailable is INTERNAL, never a refusal", async () => {
|
|
92
|
+
const { authorizer } = fakeAuthorizer({
|
|
93
|
+
kind: "unavailable",
|
|
94
|
+
cause: new Error("backend down"),
|
|
95
|
+
});
|
|
96
|
+
const error = await captureError(() =>
|
|
97
|
+
newAuthorizeMemberAudienceStep(authorizer).execute(ctxFor("acme")),
|
|
98
|
+
);
|
|
99
|
+
expect(error.code).toBe(Code.Internal);
|
|
100
|
+
expect(error.rawMessage).toBe(AUTHORIZATION_UNAVAILABLE_MESSAGE);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("an empty org asks nothing (the loader's INVALID_ARGUMENT answers), and the internal class skips", async () => {
|
|
104
|
+
const denying = fakeAuthorizer({ kind: "deny", reason: "" });
|
|
105
|
+
await newAuthorizeMemberAudienceStep(denying.authorizer).execute(
|
|
106
|
+
ctxFor(""),
|
|
107
|
+
);
|
|
108
|
+
await newAuthorizeMemberAudienceStep(denying.authorizer).execute(
|
|
109
|
+
ctxFor("acme", testCallerIdentity({ callerClass: "internal" })),
|
|
110
|
+
);
|
|
111
|
+
expect(denying.checks).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -60,6 +60,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
60
60
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
61
61
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
62
62
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
63
|
+
import {
|
|
64
|
+
loadedTargetAsMethod,
|
|
65
|
+
newAuthorizeResolvedTargetStep,
|
|
66
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
63
67
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
64
68
|
import {
|
|
65
69
|
newBuildNewStateStep,
|
|
@@ -109,6 +113,8 @@ import {
|
|
|
109
113
|
newValidateShareUpdateStep,
|
|
110
114
|
sharedNotFound,
|
|
111
115
|
sharingLinkTokenAllowed,
|
|
116
|
+
resolveShareCreateTargets,
|
|
117
|
+
newAuthorizeMemberAudienceStep,
|
|
112
118
|
} from "./steps.js";
|
|
113
119
|
|
|
114
120
|
export interface AgentShareControllerDeps {
|
|
@@ -176,6 +182,13 @@ async function createShare(
|
|
|
176
182
|
.addStep(newValidateProtoStep())
|
|
177
183
|
.addStep(newValidateVisibilityStep())
|
|
178
184
|
.addStep(newResolveShareDefaultsStep(deps.store))
|
|
185
|
+
// Before the duplicate check (steps.ts, resolveShareCreateTargets).
|
|
186
|
+
.addStep(
|
|
187
|
+
newAuthorizeResolvedTargetStep(
|
|
188
|
+
deps.authorizer,
|
|
189
|
+
resolveShareCreateTargets,
|
|
190
|
+
),
|
|
191
|
+
)
|
|
179
192
|
.addStep(newResolveSlugStep())
|
|
180
193
|
.addStep(newCheckDuplicateStep(deps.store))
|
|
181
194
|
.addStep(newBuildNewStateStep())
|
|
@@ -491,6 +504,12 @@ async function getByReference(
|
|
|
491
504
|
)
|
|
492
505
|
.addStep(newValidateProtoStep())
|
|
493
506
|
.addStep(newLoadByReferenceStep(deps.store, AgentShareSchema))
|
|
507
|
+
.addStep(
|
|
508
|
+
newAuthorizeResolvedTargetStep(
|
|
509
|
+
deps.authorizer,
|
|
510
|
+
loadedTargetAsMethod(AgentShareQueryController.method.get),
|
|
511
|
+
),
|
|
512
|
+
)
|
|
494
513
|
.build()
|
|
495
514
|
.execute(reqCtx);
|
|
496
515
|
return reqCtx.get(TARGET_RESOURCE_KEY) as AgentShare;
|
|
@@ -797,6 +816,8 @@ async function getSharedProfileForMember(
|
|
|
797
816
|
),
|
|
798
817
|
)
|
|
799
818
|
.addStep(newValidateProtoStep())
|
|
819
|
+
// Membership before existence (steps.ts, AuthorizeMemberAudience).
|
|
820
|
+
.addStep(newAuthorizeMemberAudienceStep(deps.authorizer))
|
|
800
821
|
.addStep(newLoadShareForMemberProfileStep(deps.store))
|
|
801
822
|
.addStep(newProjectMemberSharedProfileStep(deps.store))
|
|
802
823
|
.build()
|
|
@@ -35,6 +35,7 @@ import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb"
|
|
|
35
35
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
36
36
|
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
37
37
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
38
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
38
39
|
|
|
39
40
|
import {
|
|
40
41
|
failedPreconditionError,
|
|
@@ -44,6 +45,13 @@ import {
|
|
|
44
45
|
} from "../../pipeline/errors.js";
|
|
45
46
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
46
47
|
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
48
|
+
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
49
|
+
import {
|
|
50
|
+
AUTHORIZATION_UNAVAILABLE_MESSAGE,
|
|
51
|
+
evaluateAuthorizer,
|
|
52
|
+
} from "../../pipeline/steps/authorize.js";
|
|
53
|
+
import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
|
|
54
|
+
import type { GetByReferenceDesc } from "../../pipeline/steps/authorize-resolved-target.js";
|
|
47
55
|
import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
|
|
48
56
|
import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
|
|
49
57
|
import type { Store } from "../../store/interface.js";
|
|
@@ -61,10 +69,66 @@ type AgentShareDesc = typeof AgentShareSchema;
|
|
|
61
69
|
/**
|
|
62
70
|
* Context key for the agent resolved from spec.agent_ref during
|
|
63
71
|
* create/apply, so later steps never re-load it — Go referencedAgentKey.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
72
|
+
* ResolveShareDefaults writes it; the create lane's authorization step and
|
|
73
|
+
* StampAgentPin read it.
|
|
66
74
|
*/
|
|
67
|
-
const REFERENCED_AGENT_KEY = "agentShareReferencedAgent";
|
|
75
|
+
export const REFERENCED_AGENT_KEY = "agentShareReferencedAgent";
|
|
76
|
+
|
|
77
|
+
/** The deny copy of the agent's bar, the Java AgentShareCreateHandler's. */
|
|
78
|
+
export const SHARE_AGENT_DENIED_MESSAGE =
|
|
79
|
+
"You don't have permission to share this agent";
|
|
80
|
+
|
|
81
|
+
/** The deny copy of the sharing organization's bar, the Java handler's. */
|
|
82
|
+
export const SHARE_ORGANIZATION_DENIED_MESSAGE =
|
|
83
|
+
"You don't have permission to create agent shares in this organization";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The create lane's authorization questions, for AuthorizeResolvedTarget
|
|
87
|
+
* after ResolveShareDefaults: the two-arm consent bar the Java handler
|
|
88
|
+
* enforced and the model's comment on can_create_agent_share describes.
|
|
89
|
+
* The RPC is is_skip_authorization because the target is the referenced
|
|
90
|
+
* agent, resolved from a slug, not a request field.
|
|
91
|
+
*
|
|
92
|
+
* A same-org share (spec.agent_ref.org equals metadata.org, after the
|
|
93
|
+
* resolve step made the reference absolute) asks can_edit on the agent:
|
|
94
|
+
* sharing puts the agent in front of a wider audience, an editor's act.
|
|
95
|
+
*
|
|
96
|
+
* A cross-org share of another organization's public agent asks two
|
|
97
|
+
* questions, agent first as Java did: can_execute on the agent (whoever may
|
|
98
|
+
* run it may offer it) and then can_create_agent_share on the SHARING
|
|
99
|
+
* organization — a public share spends that organization's credits on the
|
|
100
|
+
* open internet, an admin-level act. The cross-org validity rules (public
|
|
101
|
+
* agent, no org audience, public dependencies) stay the resolve step's.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveShareCreateTargets(
|
|
104
|
+
ctx: RequestContext<AgentShareDesc>,
|
|
105
|
+
): ReadonlyArray<AuthorizationTarget> {
|
|
106
|
+
const agent = ctx.get(REFERENCED_AGENT_KEY) as Agent | undefined;
|
|
107
|
+
if (agent === undefined) {
|
|
108
|
+
throw new Error("referenced agent not found in context");
|
|
109
|
+
}
|
|
110
|
+
const share = ctx.newState;
|
|
111
|
+
const shareOrg = share.metadata?.org ?? "";
|
|
112
|
+
const crossOrg = (share.spec?.agentRef?.org ?? "") !== shareOrg;
|
|
113
|
+
const agentBar: AuthorizationTarget = {
|
|
114
|
+
permission: crossOrg ? IamPermission.can_execute : IamPermission.can_edit,
|
|
115
|
+
resourceKind: ApiResourceKind.agent,
|
|
116
|
+
resourceId: agent.metadata?.id ?? "",
|
|
117
|
+
deniedMessage: SHARE_AGENT_DENIED_MESSAGE,
|
|
118
|
+
};
|
|
119
|
+
if (!crossOrg) {
|
|
120
|
+
return [agentBar];
|
|
121
|
+
}
|
|
122
|
+
return [
|
|
123
|
+
agentBar,
|
|
124
|
+
{
|
|
125
|
+
permission: IamPermission.can_create_agent_share,
|
|
126
|
+
resourceKind: ApiResourceKind.organization,
|
|
127
|
+
resourceId: shareOrg,
|
|
128
|
+
deniedMessage: SHARE_ORGANIZATION_DENIED_MESSAGE,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
}
|
|
68
132
|
|
|
69
133
|
/**
|
|
70
134
|
* The single refusal for every anonymous/member resolution miss: share
|
|
@@ -78,6 +142,71 @@ export function sharedNotFound(slug: string): ConnectError {
|
|
|
78
142
|
return notFoundError("Agent", slug);
|
|
79
143
|
}
|
|
80
144
|
|
|
145
|
+
/**
|
|
146
|
+
* AuthorizeMemberAudience — the member-profile lane's gate, membership
|
|
147
|
+
* BEFORE existence: the Java AgentShareGetSharedProfileForMemberHandler
|
|
148
|
+
* order. getSharedProfileForMember is the signed-in resolution path for a
|
|
149
|
+
* share URL, and the proto pins its contract: a share that does not exist,
|
|
150
|
+
* is disabled, or is asked for by someone who is not a member of the
|
|
151
|
+
* sharing organization all answer the SAME NOT_FOUND, so a share URL
|
|
152
|
+
* teaches a non-member nothing — not even whether the share exists. A
|
|
153
|
+
* non-member resolving a public share uses the anonymous lane instead.
|
|
154
|
+
*
|
|
155
|
+
* The question is can_view on the organization named in the reference (the
|
|
156
|
+
* viewer set: members and above), asked live on every call so a revoked
|
|
157
|
+
* member loses access at once; the RPC is is_skip_authorization because
|
|
158
|
+
* the target is the reference's organization, not a request field the
|
|
159
|
+
* annotation can key on. This is deliberately NOT the shared
|
|
160
|
+
* AuthorizeResolvedTarget step: that step answers deny with
|
|
161
|
+
* PERMISSION_DENIED, and this lane's wire contract is the indistinguishable
|
|
162
|
+
* NOT_FOUND above, so the lane keeps its own mapping over the same
|
|
163
|
+
* evaluation (the decision form checkMyPermission uses). An empty org is
|
|
164
|
+
* left to the loader's INVALID_ARGUMENT, the proto's own copy for it; an
|
|
165
|
+
* unavailable authorizer is INTERNAL, never softened into a refusal; the
|
|
166
|
+
* `internal` class is exempt as everywhere.
|
|
167
|
+
*/
|
|
168
|
+
export function newAuthorizeMemberAudienceStep(
|
|
169
|
+
authorizer: Authorizer,
|
|
170
|
+
): PipelineStep<GetByReferenceDesc> {
|
|
171
|
+
return {
|
|
172
|
+
name: "AuthorizeMemberAudience",
|
|
173
|
+
async execute(ctx: RequestContext<GetByReferenceDesc>): Promise<void> {
|
|
174
|
+
const ref = ctx.input;
|
|
175
|
+
if (ref.org === "" || ctx.callerIdentity.callerClass === "internal") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const decision = await evaluateAuthorizer(
|
|
179
|
+
authorizer,
|
|
180
|
+
ctx.callerIdentity,
|
|
181
|
+
{
|
|
182
|
+
permission: IamPermission.can_view,
|
|
183
|
+
resourceKind: ApiResourceKind.organization,
|
|
184
|
+
resourceId: ref.org,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
switch (decision.kind) {
|
|
188
|
+
case "allow":
|
|
189
|
+
return;
|
|
190
|
+
case "deny":
|
|
191
|
+
case "not-found":
|
|
192
|
+
throw sharedNotFound(ref.slug);
|
|
193
|
+
case "unavailable":
|
|
194
|
+
throw internalError(
|
|
195
|
+
decision.cause,
|
|
196
|
+
AUTHORIZATION_UNAVAILABLE_MESSAGE,
|
|
197
|
+
);
|
|
198
|
+
default: {
|
|
199
|
+
const exhaustive: never = decision;
|
|
200
|
+
throw internalError(
|
|
201
|
+
new Error(`unknown decision ${JSON.stringify(exhaustive)}`),
|
|
202
|
+
AUTHORIZATION_UNAVAILABLE_MESSAGE,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
81
210
|
/**
|
|
82
211
|
* The link-token predicate — Go sharingLinkTokenAllowed, the mirror of the
|
|
83
212
|
* cloud edition's SharingLinkTokenPolicy:
|
|
@@ -90,7 +219,10 @@ export function sharedNotFound(slug: string): ConnectError {
|
|
|
90
219
|
* guard restores Go's semantics; the length of a token is not secret (all
|
|
91
220
|
* server-minted tokens are 27 chars).
|
|
92
221
|
*/
|
|
93
|
-
export function sharingLinkTokenAllowed(
|
|
222
|
+
export function sharingLinkTokenAllowed(
|
|
223
|
+
presented: string,
|
|
224
|
+
live: string,
|
|
225
|
+
): boolean {
|
|
94
226
|
if (live === "") {
|
|
95
227
|
return true;
|
|
96
228
|
}
|
|
@@ -182,7 +314,9 @@ export async function findShareByOrgAndSlug(
|
|
|
182
314
|
* provided neither — the canonical share keeps the agent's hosted URL.
|
|
183
315
|
* Runs before ResolveSlug, which skips already-set slugs.
|
|
184
316
|
*/
|
|
185
|
-
export function newResolveShareDefaultsStep(
|
|
317
|
+
export function newResolveShareDefaultsStep(
|
|
318
|
+
store: Store,
|
|
319
|
+
): PipelineStep<AgentShareDesc> {
|
|
186
320
|
return {
|
|
187
321
|
name: "ResolveShareDefaults",
|
|
188
322
|
async execute(ctx: RequestContext<AgentShareDesc>): Promise<void> {
|
|
@@ -204,7 +338,11 @@ export function newResolveShareDefaultsStep(store: Store): PipelineStep<AgentSha
|
|
|
204
338
|
agentRef!.org = metadata!.org;
|
|
205
339
|
}
|
|
206
340
|
|
|
207
|
-
const agent = await findAgentByOrgAndSlug(
|
|
341
|
+
const agent = await findAgentByOrgAndSlug(
|
|
342
|
+
store,
|
|
343
|
+
agentRef!.org,
|
|
344
|
+
agentRef!.slug,
|
|
345
|
+
);
|
|
208
346
|
if (agent === undefined) {
|
|
209
347
|
throw notFoundError("Agent", agentRef!.slug);
|
|
210
348
|
}
|
|
@@ -288,7 +426,10 @@ async function findNonPublicDependencies(
|
|
|
288
426
|
|
|
289
427
|
const seen = new Set<string>();
|
|
290
428
|
const deps: DepRef[] = [];
|
|
291
|
-
const add = (
|
|
429
|
+
const add = (
|
|
430
|
+
kind: ApiResourceKind,
|
|
431
|
+
ref: ApiResourceReference | undefined,
|
|
432
|
+
): void => {
|
|
292
433
|
if (ref === undefined || ref.slug === "") {
|
|
293
434
|
return;
|
|
294
435
|
}
|
|
@@ -320,8 +461,20 @@ async function findNonPublicDependencies(
|
|
|
320
461
|
// are the only dependency kinds an agent blueprint declares.
|
|
321
462
|
const resolved =
|
|
322
463
|
dep.kind === ApiResourceKind.skill
|
|
323
|
-
? await findResourceBySlug(
|
|
324
|
-
|
|
464
|
+
? await findResourceBySlug(
|
|
465
|
+
store,
|
|
466
|
+
dep.kind,
|
|
467
|
+
SkillSchema,
|
|
468
|
+
dep.slug,
|
|
469
|
+
dep.org,
|
|
470
|
+
)
|
|
471
|
+
: await findResourceBySlug(
|
|
472
|
+
store,
|
|
473
|
+
dep.kind,
|
|
474
|
+
McpServerSchema,
|
|
475
|
+
dep.slug,
|
|
476
|
+
dep.org,
|
|
477
|
+
);
|
|
325
478
|
visibility = resolved?.metadata?.visibility;
|
|
326
479
|
} catch (error) {
|
|
327
480
|
throw internalError(
|
|
@@ -396,20 +549,28 @@ export function newValidateShareUpdateStep(): PipelineStep<AgentShareDesc> {
|
|
|
396
549
|
// Normalize the input ref's org the same way create does (empty
|
|
397
550
|
// means the share's own org) before comparing.
|
|
398
551
|
const inputOrg =
|
|
399
|
-
(inputRef?.org ?? "") !== ""
|
|
552
|
+
(inputRef?.org ?? "") !== ""
|
|
553
|
+
? inputRef!.org
|
|
554
|
+
: (existing.metadata?.org ?? "");
|
|
400
555
|
|
|
401
556
|
if (
|
|
402
557
|
(inputRef?.slug ?? "") !== (existingRef?.slug ?? "") ||
|
|
403
558
|
inputOrg !== (existingRef?.org ?? "")
|
|
404
559
|
) {
|
|
405
560
|
throw failedPreconditionError(
|
|
406
|
-
agentRefImmutableMessage(
|
|
561
|
+
agentRefImmutableMessage(
|
|
562
|
+
existingRef?.org ?? "",
|
|
563
|
+
existingRef?.slug ?? "",
|
|
564
|
+
),
|
|
407
565
|
);
|
|
408
566
|
}
|
|
409
567
|
|
|
410
|
-
const isCrossOrg =
|
|
568
|
+
const isCrossOrg =
|
|
569
|
+
(existingRef?.org ?? "") !== (existing.metadata?.org ?? "");
|
|
411
570
|
if (isCrossOrg && ctx.input.spec?.audience === AgentShareAudience.org) {
|
|
412
|
-
throw failedPreconditionError(
|
|
571
|
+
throw failedPreconditionError(
|
|
572
|
+
crossOrgAudienceMessage(existingRef?.org ?? ""),
|
|
573
|
+
);
|
|
413
574
|
}
|
|
414
575
|
},
|
|
415
576
|
};
|
|
@@ -430,7 +591,11 @@ export async function buildSharedAgentProfile(
|
|
|
430
591
|
share: AgentShare,
|
|
431
592
|
): Promise<SharedAgentProfile> {
|
|
432
593
|
const ref = share.spec?.agentRef;
|
|
433
|
-
const agent = await findAgentByOrgAndSlug(
|
|
594
|
+
const agent = await findAgentByOrgAndSlug(
|
|
595
|
+
store,
|
|
596
|
+
ref?.org ?? "",
|
|
597
|
+
ref?.slug ?? "",
|
|
598
|
+
);
|
|
434
599
|
if (agent === undefined) {
|
|
435
600
|
throw sharedNotFound(share.metadata?.slug ?? "");
|
|
436
601
|
}
|
|
@@ -440,7 +605,8 @@ export async function buildSharedAgentProfile(
|
|
|
440
605
|
throw sharedNotFound(share.metadata?.slug ?? "");
|
|
441
606
|
}
|
|
442
607
|
|
|
443
|
-
const isCrossOrg =
|
|
608
|
+
const isCrossOrg =
|
|
609
|
+
(share.metadata?.org ?? "") !== (agent.metadata?.org ?? "");
|
|
444
610
|
if (
|
|
445
611
|
isCrossOrg &&
|
|
446
612
|
agent.metadata?.visibility !== ApiResourceVisibility.visibility_public
|
|
@@ -66,9 +66,9 @@ import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
|
66
66
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
67
67
|
import {
|
|
68
68
|
authorizeDirect,
|
|
69
|
-
authorizeResolvedResource,
|
|
70
69
|
newAuthorizeStep,
|
|
71
70
|
} from "../../pipeline/steps/authorize.js";
|
|
71
|
+
import { newAuthorizeResolvedTargetStep } from "../../pipeline/steps/authorize-resolved-target.js";
|
|
72
72
|
import {
|
|
73
73
|
generateId,
|
|
74
74
|
setAuditFieldsForCreate,
|
|
@@ -426,23 +426,18 @@ async function listByExecution(
|
|
|
426
426
|
}
|
|
427
427
|
},
|
|
428
428
|
})
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// divergence from Java's deny-everyone arm on this lane.
|
|
442
|
-
const input = stepCtx.input;
|
|
443
|
-
await authorizeResolvedResource(
|
|
444
|
-
deps.authorizer,
|
|
445
|
-
stepCtx.callerIdentity,
|
|
429
|
+
// The Java ArtifactListByExecutionHandler's gate: can_view on the PARENT
|
|
430
|
+
// execution — "if you can view an execution, you can list its
|
|
431
|
+
// artifacts." The two-field dispatch (workflow-execution first, the Java
|
|
432
|
+
// order) is why the declarative annotation cannot express this lane and
|
|
433
|
+
// the proto carries is_skip_authorization. Deny copy is the Java
|
|
434
|
+
// handler's byte-pinned error_msg; a nonexistent id answers NOT_FOUND
|
|
435
|
+
// through the authorizer's probe — the ruled uniform unknown-id
|
|
436
|
+
// posture, a recorded divergence from Java's deny-everyone arm here.
|
|
437
|
+
.addStep(
|
|
438
|
+
newAuthorizeResolvedTargetStep(
|
|
439
|
+
deps.authorizer,
|
|
440
|
+
({ input }) => [
|
|
446
441
|
{
|
|
447
442
|
permission: IamPermission.can_view,
|
|
448
443
|
resourceKind:
|
|
@@ -453,11 +448,12 @@ async function listByExecution(
|
|
|
453
448
|
input.workflowExecutionId !== ""
|
|
454
449
|
? input.workflowExecutionId
|
|
455
450
|
: input.agentExecutionId,
|
|
451
|
+
deniedMessage: "unauthorized to list artifacts for this execution",
|
|
456
452
|
},
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
453
|
+
],
|
|
454
|
+
"AuthorizeParentExecution",
|
|
455
|
+
),
|
|
456
|
+
)
|
|
461
457
|
.addStep({
|
|
462
458
|
name: "QueryArtifactsByExecution",
|
|
463
459
|
async execute(stepCtx): Promise<void> {
|
|
@@ -51,6 +51,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
51
51
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
52
52
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
53
53
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
54
|
+
import {
|
|
55
|
+
loadedTargetAsMethod,
|
|
56
|
+
newAuthorizeResolvedTargetStep,
|
|
57
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
54
58
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
55
59
|
import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
|
|
56
60
|
import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
|
|
@@ -369,6 +373,12 @@ async function getByReference(
|
|
|
369
373
|
)
|
|
370
374
|
.addStep(newValidateProtoStep())
|
|
371
375
|
.addStep(newLoadByReferenceStep(deps.store, ChannelAppSchema))
|
|
376
|
+
.addStep(
|
|
377
|
+
newAuthorizeResolvedTargetStep(
|
|
378
|
+
deps.authorizer,
|
|
379
|
+
loadedTargetAsMethod(ChannelAppQueryController.method.get),
|
|
380
|
+
),
|
|
381
|
+
)
|
|
372
382
|
.build()
|
|
373
383
|
.execute(reqCtx);
|
|
374
384
|
const app = reqCtx.get(TARGET_RESOURCE_KEY) as ChannelApp;
|
|
@@ -58,6 +58,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
58
58
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
59
59
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
60
60
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
61
|
+
import {
|
|
62
|
+
loadedTargetAsMethod,
|
|
63
|
+
newAuthorizeResolvedTargetStep,
|
|
64
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
61
65
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
62
66
|
import { newAuthorizeVisibilityTransitionStep } from "../../pipeline/steps/visibility-gates.js";
|
|
63
67
|
import {
|
|
@@ -682,6 +686,12 @@ async function getByReference(
|
|
|
682
686
|
)
|
|
683
687
|
.addStep(newValidateProtoStep())
|
|
684
688
|
.addStep(newLoadByReferenceStep(deps.store, EnvironmentSchema))
|
|
689
|
+
.addStep(
|
|
690
|
+
newAuthorizeResolvedTargetStep(
|
|
691
|
+
deps.authorizer,
|
|
692
|
+
loadedTargetAsMethod(EnvironmentQueryController.method.get),
|
|
693
|
+
),
|
|
694
|
+
)
|
|
685
695
|
.build()
|
|
686
696
|
.execute(reqCtx);
|
|
687
697
|
const env = reqCtx.get(TARGET_RESOURCE_KEY) as Environment;
|
|
@@ -56,6 +56,10 @@ import { newPipeline } from "../../pipeline/pipeline.js";
|
|
|
56
56
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
57
57
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
58
58
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
59
|
+
import {
|
|
60
|
+
loadedTargetAsMethod,
|
|
61
|
+
newAuthorizeResolvedTargetStep,
|
|
62
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
59
63
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
60
64
|
import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
|
|
61
65
|
import {
|
|
@@ -361,6 +365,12 @@ async function getByReference(
|
|
|
361
365
|
)
|
|
362
366
|
.addStep(newValidateProtoStep())
|
|
363
367
|
.addStep(newLoadByReferenceStep(deps.store, ExecutionContextSchema))
|
|
368
|
+
.addStep(
|
|
369
|
+
newAuthorizeResolvedTargetStep(
|
|
370
|
+
deps.authorizer,
|
|
371
|
+
loadedTargetAsMethod(ExecutionContextQueryController.method.get),
|
|
372
|
+
),
|
|
373
|
+
)
|
|
364
374
|
.build()
|
|
365
375
|
.execute(reqCtx);
|
|
366
376
|
const ec = reqCtx.get(TARGET_RESOURCE_KEY) as ExecutionContext;
|
|
@@ -34,6 +34,7 @@ import { isCiphertextShaped } from "../../encryption/encryption.js";
|
|
|
34
34
|
import type { SecretService } from "../../encryption/encryption.js";
|
|
35
35
|
import { EncryptionScope } from "../../encryption/encryption.js";
|
|
36
36
|
import type { Authorizer, AuthzDecision } from "../../extensions/authorizer.js";
|
|
37
|
+
import { isServerComposedRequest } from "../../extensions/identity.js";
|
|
37
38
|
import {
|
|
38
39
|
internalError,
|
|
39
40
|
invalidArgumentError,
|
|
@@ -240,7 +241,7 @@ export function newAuthorizeExecutionContextCreateStep(
|
|
|
240
241
|
ctx: RequestContext<typeof ExecutionContextSchema>,
|
|
241
242
|
): Promise<void> {
|
|
242
243
|
const caller = ctx.callerIdentity;
|
|
243
|
-
if (caller
|
|
244
|
+
if (isServerComposedRequest(caller)) {
|
|
244
245
|
return; // authorized upstream — the Java in-process trust arm
|
|
245
246
|
}
|
|
246
247
|
let decision: AuthzDecision;
|
|
@@ -254,13 +254,6 @@ export async function connect(
|
|
|
254
254
|
input: ConnectInput,
|
|
255
255
|
identity: CallerIdentity,
|
|
256
256
|
): Promise<McpServer> {
|
|
257
|
-
const engineState = deps.engineState();
|
|
258
|
-
if (!engineState.connected) {
|
|
259
|
-
throw failedPreconditionError(
|
|
260
|
-
"connect is not available: Temporal not configured",
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
257
|
const mcpServerId = input.mcpServerId;
|
|
265
258
|
if (mcpServerId === "") {
|
|
266
259
|
throw invalidArgumentError("mcp_server_id is required");
|
|
@@ -280,9 +273,10 @@ export async function connect(
|
|
|
280
273
|
throw notFoundError("mcp_server", mcpServerId);
|
|
281
274
|
}
|
|
282
275
|
|
|
283
|
-
// The annotation's can_connect check
|
|
284
|
-
// McpServerConnectHandler
|
|
285
|
-
//
|
|
276
|
+
// The annotation's can_connect check after the load and BEFORE the engine
|
|
277
|
+
// check (the Java McpServerConnectHandler load-before-authorize order,
|
|
278
|
+
// stigmer#224): a caller who may not connect this server learns nothing
|
|
279
|
+
// about whether the engine is up.
|
|
286
280
|
await authorizeDirect(
|
|
287
281
|
McpServerCommandController.method.connect,
|
|
288
282
|
deps.authorizer,
|
|
@@ -290,6 +284,13 @@ export async function connect(
|
|
|
290
284
|
input,
|
|
291
285
|
);
|
|
292
286
|
|
|
287
|
+
const engineState = deps.engineState();
|
|
288
|
+
if (!engineState.connected) {
|
|
289
|
+
throw failedPreconditionError(
|
|
290
|
+
"connect is not available: Temporal not configured",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
293
294
|
const prepared = await prepareConnect(deps, mcpServer, input, identity);
|
|
294
295
|
|
|
295
296
|
try {
|