@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The caller's personal environment as a source of DECLARED variables: the
|
|
3
|
+
* one lookup that answers "which of these declared keys does the person
|
|
4
|
+
* have saved" for every lane that runs a tool on the person's behalf.
|
|
5
|
+
*
|
|
6
|
+
* Two lanes need it and must agree on it. The MCP connect lane (the
|
|
7
|
+
* discovery run a person triggers from the console) resolves a server's
|
|
8
|
+
* declared variables here when no one-time runtime_env was supplied; the
|
|
9
|
+
* agent-execution ExecutionContext build resolves a SESSION-level MCP
|
|
10
|
+
* server's declared variables here when the merge chain did not carry them,
|
|
11
|
+
* because a session's own servers ride no agent instance and so have no
|
|
12
|
+
* environment_refs of their own. Before the build learned this rule a key
|
|
13
|
+
* saved through the console's "save for future" reached the connect lane
|
|
14
|
+
* and never the run.
|
|
15
|
+
*
|
|
16
|
+
* Least privilege by construction: only the keys the caller declares are
|
|
17
|
+
* read, one GetSecretValue per key; nothing here layers a whole
|
|
18
|
+
* environment onto anything. What a missing personal environment or a
|
|
19
|
+
* missing required key MEANS is the caller's to decide — connect refuses
|
|
20
|
+
* (a person asked to connect and cannot without the credential), the
|
|
21
|
+
* build warns (the run fails at the tool with a clearer error) — so both
|
|
22
|
+
* outcomes are returned as values, never thrown. A failing list read is
|
|
23
|
+
* the one infrastructure fault, thrown as Internal.
|
|
24
|
+
*
|
|
25
|
+
* Proven by __tests__/personal.test.ts; the connect lane's wire copy over
|
|
26
|
+
* these outcomes by mcpserver/__tests__/connect.test.ts.
|
|
27
|
+
*/
|
|
28
|
+
import { create } from "@bufbuild/protobuf";
|
|
29
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
30
|
+
|
|
31
|
+
import type { EnvironmentList } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
|
|
32
|
+
import type {
|
|
33
|
+
EnvironmentSecretValueInputSchema,
|
|
34
|
+
ListEnvironmentsRequestSchema,
|
|
35
|
+
} from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
|
|
36
|
+
import type {
|
|
37
|
+
EnvVarDeclaration,
|
|
38
|
+
EnvironmentValue,
|
|
39
|
+
} from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
|
|
40
|
+
import type { ExecutionValue } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
|
|
41
|
+
import { ExecutionValueSchema } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
|
|
42
|
+
|
|
43
|
+
import type { Logger } from "../../boot/logger.js";
|
|
44
|
+
import { internalError } from "../../pipeline/errors.js";
|
|
45
|
+
import { PERSONAL_LABEL_KEY, PERSONAL_LABEL_VALUE } from "./constants.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The narrow environment read surface this lookup consumes: the list RPC
|
|
49
|
+
* (to find the personal environment by its label) and the secret read
|
|
50
|
+
* (decrypted; the ordinary Environment surface redacts, oss#405). Both
|
|
51
|
+
* consuming domains' in-process edges already satisfy it.
|
|
52
|
+
*/
|
|
53
|
+
export interface PersonalEnvironmentReader {
|
|
54
|
+
list(
|
|
55
|
+
request: MessageInitShape<typeof ListEnvironmentsRequestSchema>,
|
|
56
|
+
): Promise<EnvironmentList>;
|
|
57
|
+
getSecretValue(
|
|
58
|
+
input: MessageInitShape<typeof EnvironmentSecretValueInputSchema>,
|
|
59
|
+
): Promise<EnvironmentValue>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What the lookup found; the caller decides what each outcome means. */
|
|
63
|
+
export type PersonalEnvironmentResolution =
|
|
64
|
+
| { readonly kind: "no-personal-environment" }
|
|
65
|
+
| {
|
|
66
|
+
readonly kind: "resolved";
|
|
67
|
+
/** The declared keys the personal environment held, as execution values. */
|
|
68
|
+
readonly values: { readonly [key: string]: ExecutionValue };
|
|
69
|
+
/**
|
|
70
|
+
* The REQUIRED declared keys the personal environment did not hold
|
|
71
|
+
* (absent, unreadable, or empty). Optional keys never appear here.
|
|
72
|
+
*/
|
|
73
|
+
readonly missing: readonly string[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the given declarations against the caller's personal
|
|
78
|
+
* environment in `org`. A declaration's `is_secret` rides onto the value;
|
|
79
|
+
* a declared key the environment lacks is skipped when optional and
|
|
80
|
+
* reported in `missing` when required. A secret read that fails is logged
|
|
81
|
+
* and treated as absent, the same way for both lanes.
|
|
82
|
+
*/
|
|
83
|
+
export async function resolveDeclaredFromPersonalEnvironment(
|
|
84
|
+
reader: PersonalEnvironmentReader,
|
|
85
|
+
logger: Logger,
|
|
86
|
+
org: string,
|
|
87
|
+
declarations: { readonly [key: string]: EnvVarDeclaration },
|
|
88
|
+
): Promise<PersonalEnvironmentResolution> {
|
|
89
|
+
let listResponse: EnvironmentList;
|
|
90
|
+
try {
|
|
91
|
+
listResponse = await reader.list({
|
|
92
|
+
org,
|
|
93
|
+
labels: { [PERSONAL_LABEL_KEY]: PERSONAL_LABEL_VALUE },
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw internalError(error, "failed to list personal environments");
|
|
97
|
+
}
|
|
98
|
+
if (listResponse.totalCount === 0 || listResponse.items.length === 0) {
|
|
99
|
+
return { kind: "no-personal-environment" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const personalEnv = listResponse.items[0];
|
|
103
|
+
const personalEnvId = personalEnv?.metadata?.id ?? "";
|
|
104
|
+
// The stored keys are present even when their values are redacted, so
|
|
105
|
+
// existence is checkable before the secret read. Own-key membership
|
|
106
|
+
// (never the prototype chain): an exotic key name must not read as held.
|
|
107
|
+
const stored = personalEnv?.spec?.data ?? {};
|
|
108
|
+
|
|
109
|
+
const values: { [key: string]: ExecutionValue } = {};
|
|
110
|
+
const missing: string[] = [];
|
|
111
|
+
for (const [key, decl] of Object.entries(declarations)) {
|
|
112
|
+
if (!Object.hasOwn(stored, key)) {
|
|
113
|
+
if (!decl.optional) missing.push(key);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
let secretValue: EnvironmentValue;
|
|
117
|
+
try {
|
|
118
|
+
secretValue = await reader.getSecretValue({
|
|
119
|
+
environmentId: personalEnvId,
|
|
120
|
+
key,
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.warn("Failed to get secret value from personal environment", {
|
|
124
|
+
key,
|
|
125
|
+
personal_env_id: personalEnvId,
|
|
126
|
+
error: error instanceof Error ? error.message : String(error),
|
|
127
|
+
});
|
|
128
|
+
if (!decl.optional) missing.push(key);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (secretValue.value === "") {
|
|
132
|
+
if (!decl.optional) missing.push(key);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
values[key] = create(ExecutionValueSchema, {
|
|
136
|
+
value: secretValue.value,
|
|
137
|
+
isSecret: decl.isSecret,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return { kind: "resolved", values, missing };
|
|
141
|
+
}
|
|
@@ -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;
|
|
@@ -42,6 +42,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
|
|
|
42
42
|
import { TokenEndpointAuthMethod } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
43
43
|
|
|
44
44
|
import type { Logger } from "../../boot/logger.js";
|
|
45
|
+
import { resolveDeclaredFromPersonalEnvironment } from "../environment/personal.js";
|
|
45
46
|
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
46
47
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
47
48
|
import { authorizeDirect } from "../../pipeline/steps/authorize.js";
|
|
@@ -150,9 +151,6 @@ export const ASYNC_CONNECT_TIMEOUT: ConnectBudget = {
|
|
|
150
151
|
*/
|
|
151
152
|
export const BEST_EFFORT_CONNECT_GET_BUFFER_MS = 15_000;
|
|
152
153
|
|
|
153
|
-
/** The label selecting the user's personal environment (connect.go:74). */
|
|
154
|
-
export const PERSONAL_ENV_LABEL = "stigmer.ai/personal";
|
|
155
|
-
|
|
156
154
|
/**
|
|
157
155
|
* The narrow environment read surface the connect lanes consume for
|
|
158
156
|
* personal-environment resolution — satisfied by the composition root's
|
|
@@ -256,13 +254,6 @@ export async function connect(
|
|
|
256
254
|
input: ConnectInput,
|
|
257
255
|
identity: CallerIdentity,
|
|
258
256
|
): Promise<McpServer> {
|
|
259
|
-
const engineState = deps.engineState();
|
|
260
|
-
if (!engineState.connected) {
|
|
261
|
-
throw failedPreconditionError(
|
|
262
|
-
"connect is not available: Temporal not configured",
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
257
|
const mcpServerId = input.mcpServerId;
|
|
267
258
|
if (mcpServerId === "") {
|
|
268
259
|
throw invalidArgumentError("mcp_server_id is required");
|
|
@@ -282,9 +273,10 @@ export async function connect(
|
|
|
282
273
|
throw notFoundError("mcp_server", mcpServerId);
|
|
283
274
|
}
|
|
284
275
|
|
|
285
|
-
// The annotation's can_connect check
|
|
286
|
-
// McpServerConnectHandler
|
|
287
|
-
//
|
|
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.
|
|
288
280
|
await authorizeDirect(
|
|
289
281
|
McpServerCommandController.method.connect,
|
|
290
282
|
deps.authorizer,
|
|
@@ -292,6 +284,13 @@ export async function connect(
|
|
|
292
284
|
input,
|
|
293
285
|
);
|
|
294
286
|
|
|
287
|
+
const engineState = deps.engineState();
|
|
288
|
+
if (!engineState.connected) {
|
|
289
|
+
throw failedPreconditionError(
|
|
290
|
+
"connect is not available: Temporal not configured",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
295
294
|
const prepared = await prepareConnect(deps, mcpServer, input, identity);
|
|
296
295
|
|
|
297
296
|
try {
|
|
@@ -687,34 +686,29 @@ async function resolveOAuthVarsFromManagedEnv(
|
|
|
687
686
|
}
|
|
688
687
|
|
|
689
688
|
/**
|
|
690
|
-
* Reads
|
|
691
|
-
* (
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
689
|
+
* Reads a server's declared variables from the user's personal environment
|
|
690
|
+
* (Go resolveFromPersonalEnvironment) through the shared lookup in
|
|
691
|
+
* domain/environment/personal.ts, and gives its outcomes the connect
|
|
692
|
+
* lane's meaning: a person asked to connect a server and cannot without
|
|
693
|
+
* the credential, so a missing personal environment and a missing required
|
|
694
|
+
* key both refuse with FailedPrecondition. Optional variables are included
|
|
695
|
+
* when available and silently skipped when missing.
|
|
695
696
|
*/
|
|
696
697
|
async function resolveFromPersonalEnvironment(
|
|
697
698
|
deps: McpServerConnectDeps,
|
|
698
699
|
org: string,
|
|
699
700
|
envDecls: { [key: string]: EnvVarDeclaration },
|
|
700
701
|
): Promise<{ [key: string]: ExecutionValue }> {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
org,
|
|
712
|
-
labels: { [PERSONAL_ENV_LABEL]: "true" },
|
|
713
|
-
});
|
|
714
|
-
} catch (error) {
|
|
715
|
-
throw internalError(error, "failed to list personal environments");
|
|
716
|
-
}
|
|
717
|
-
if (listResponse.totalCount === 0 || listResponse.items.length === 0) {
|
|
702
|
+
const resolution = await resolveDeclaredFromPersonalEnvironment(
|
|
703
|
+
deps.environmentReader,
|
|
704
|
+
deps.logger,
|
|
705
|
+
org,
|
|
706
|
+
envDecls,
|
|
707
|
+
);
|
|
708
|
+
if (resolution.kind === "no-personal-environment") {
|
|
709
|
+
const requiredKeys = Object.entries(envDecls)
|
|
710
|
+
.filter(([, decl]) => !decl.optional)
|
|
711
|
+
.map(([key]) => key);
|
|
718
712
|
if (requiredKeys.length === 0) {
|
|
719
713
|
return {};
|
|
720
714
|
}
|
|
@@ -723,66 +717,12 @@ async function resolveFromPersonalEnvironment(
|
|
|
723
717
|
`personal environment not found for org '${org}'; save required credentials first: [${requiredKeys.join(" ")}]`,
|
|
724
718
|
);
|
|
725
719
|
}
|
|
726
|
-
|
|
727
|
-
const personalEnv = listResponse.items[0];
|
|
728
|
-
const personalEnvId = personalEnv?.metadata?.id ?? "";
|
|
729
|
-
const storedKeys = new Set(Object.keys(personalEnv?.spec?.data ?? {}));
|
|
730
|
-
|
|
731
|
-
const result: { [key: string]: ExecutionValue } = {};
|
|
732
|
-
const missing: string[] = [];
|
|
733
|
-
|
|
734
|
-
for (const [key, decl] of Object.entries(envDecls)) {
|
|
735
|
-
if (!storedKeys.has(key)) {
|
|
736
|
-
if (decl.optional) {
|
|
737
|
-
deps.logger.debug(
|
|
738
|
-
"Optional env var not in personal environment — skipping",
|
|
739
|
-
{ key },
|
|
740
|
-
);
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
missing.push(key);
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
let secretValue: EnvironmentSpecValue;
|
|
748
|
-
try {
|
|
749
|
-
secretValue = await deps.environmentReader.getSecretValue({
|
|
750
|
-
environmentId: personalEnvId,
|
|
751
|
-
key,
|
|
752
|
-
});
|
|
753
|
-
} catch (error) {
|
|
754
|
-
deps.logger.warn("Failed to get secret value from personal environment", {
|
|
755
|
-
key,
|
|
756
|
-
personal_env_id: personalEnvId,
|
|
757
|
-
error: error instanceof Error ? error.message : String(error),
|
|
758
|
-
});
|
|
759
|
-
if (decl.optional) {
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
missing.push(key);
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
if (secretValue.value === "") {
|
|
766
|
-
if (decl.optional) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
missing.push(key);
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
result[key] = create(ExecutionValueSchema, {
|
|
774
|
-
value: secretValue.value,
|
|
775
|
-
isSecret: decl.isSecret,
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (missing.length > 0) {
|
|
720
|
+
if (resolution.missing.length > 0) {
|
|
780
721
|
throw failedPreconditionError(
|
|
781
|
-
`missing required credentials in personal environment: [${missing.join(" ")}]`,
|
|
722
|
+
`missing required credentials in personal environment: [${resolution.missing.join(" ")}]`,
|
|
782
723
|
);
|
|
783
724
|
}
|
|
784
|
-
|
|
785
|
-
return result;
|
|
725
|
+
return { ...resolution.values };
|
|
786
726
|
}
|
|
787
727
|
|
|
788
728
|
/**
|
|
@@ -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,
|
|
@@ -207,7 +211,9 @@ function kindOf(ctx: HandlerContext): ApiResourceKind {
|
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
/** The endpoint-auth step's slice of the deps: the guarded fetch the connect slice holds. */
|
|
210
|
-
function endpointAuthDeps(
|
|
214
|
+
function endpointAuthDeps(
|
|
215
|
+
deps: McpServerControllerDeps,
|
|
216
|
+
): CompleteEndpointAuthDeps {
|
|
211
217
|
return { outboundFetch: deps.connect.outboundFetch, logger: deps.logger };
|
|
212
218
|
}
|
|
213
219
|
|
|
@@ -629,6 +635,12 @@ async function getByReference(
|
|
|
629
635
|
)
|
|
630
636
|
.addStep(newValidateProtoStep())
|
|
631
637
|
.addStep(newLoadByReferenceStep(deps.store, McpServerSchema))
|
|
638
|
+
.addStep(
|
|
639
|
+
newAuthorizeResolvedTargetStep(
|
|
640
|
+
deps.authorizer,
|
|
641
|
+
loadedTargetAsMethod(McpServerQueryController.method.get),
|
|
642
|
+
),
|
|
643
|
+
)
|
|
632
644
|
.addStep(newEnrichOAuthStatusStep(deps.store, deps.logger))
|
|
633
645
|
.build()
|
|
634
646
|
.execute(reqCtx);
|
|
@@ -69,13 +69,6 @@ export async function startConnect(
|
|
|
69
69
|
input: ConnectInput,
|
|
70
70
|
identity: CallerIdentity,
|
|
71
71
|
): Promise<McpServer> {
|
|
72
|
-
const engineState = deps.engineState();
|
|
73
|
-
if (!engineState.connected) {
|
|
74
|
-
throw failedPreconditionError(
|
|
75
|
-
"connect is not available: Temporal not configured",
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
const mcpServerId = input.mcpServerId;
|
|
80
73
|
if (mcpServerId === "") {
|
|
81
74
|
throw invalidArgumentError("mcp_server_id is required");
|
|
@@ -95,9 +88,10 @@ export async function startConnect(
|
|
|
95
88
|
throw notFoundError("mcp_server", mcpServerId);
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
// The annotation's can_connect check
|
|
99
|
-
//
|
|
100
|
-
//
|
|
91
|
+
// The annotation's can_connect check after the load and BEFORE the engine
|
|
92
|
+
// check (the Java McpServerStartConnectHandler load-before-authorize order,
|
|
93
|
+
// stigmer#224): a caller who may not connect this server learns nothing
|
|
94
|
+
// about whether the engine is up.
|
|
101
95
|
await authorizeDirect(
|
|
102
96
|
McpServerCommandController.method.startConnect,
|
|
103
97
|
deps.authorizer,
|
|
@@ -105,6 +99,13 @@ export async function startConnect(
|
|
|
105
99
|
input,
|
|
106
100
|
);
|
|
107
101
|
|
|
102
|
+
const engineState = deps.engineState();
|
|
103
|
+
if (!engineState.connected) {
|
|
104
|
+
throw failedPreconditionError(
|
|
105
|
+
"connect is not available: Temporal not configured",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
108
109
|
const connectStatus = mcpServer.status?.connectStatus;
|
|
109
110
|
if (connectStatus?.phase === ConnectPhase.connecting) {
|
|
110
111
|
if (
|
|
@@ -249,7 +250,12 @@ async function settleConnectAsync(
|
|
|
249
250
|
outcome.failure,
|
|
250
251
|
ASYNC_CONNECT_TIMEOUT,
|
|
251
252
|
);
|
|
252
|
-
await persistConnectFailure(
|
|
253
|
+
await persistConnectFailure(
|
|
254
|
+
deps.store,
|
|
255
|
+
deps.logger,
|
|
256
|
+
mcpServerId,
|
|
257
|
+
failure,
|
|
258
|
+
);
|
|
253
259
|
return;
|
|
254
260
|
}
|
|
255
261
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the create lane's authorization question (steps.ts,
|
|
3
|
+
* resolveMemoryCreateTargets): a wire caller asks can_create_session on
|
|
4
|
+
* metadata.org — whoever may converse in an organization may remember
|
|
5
|
+
* there — and a server-composed request (internal, or any identity that
|
|
6
|
+
* entered in-process) asks nothing, as the capture gate before it already
|
|
7
|
+
* says.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { create } from "@bufbuild/protobuf";
|
|
11
|
+
|
|
12
|
+
import { MemorySchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
|
|
13
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
15
|
+
|
|
16
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
17
|
+
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
18
|
+
import { RequestContext } from "../../../pipeline/request-context.js";
|
|
19
|
+
import {
|
|
20
|
+
MEMORY_CREATE_DENIED_MESSAGE,
|
|
21
|
+
resolveMemoryCreateTargets,
|
|
22
|
+
} from "../steps.js";
|
|
23
|
+
|
|
24
|
+
function ctxFor(caller: CallerIdentity) {
|
|
25
|
+
return new RequestContext(
|
|
26
|
+
MemorySchema,
|
|
27
|
+
create(MemorySchema, { metadata: { name: "prefers-tea", org: "acme" } }),
|
|
28
|
+
caller,
|
|
29
|
+
ApiResourceKind.memory,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("resolveMemoryCreateTargets", () => {
|
|
34
|
+
it("a wire caller asks can_create_session on the memory's organization", () => {
|
|
35
|
+
expect(resolveMemoryCreateTargets(ctxFor(testCallerIdentity()))).toEqual([
|
|
36
|
+
{
|
|
37
|
+
permission: IamPermission.can_create_session,
|
|
38
|
+
resourceKind: ApiResourceKind.organization,
|
|
39
|
+
resourceId: "acme",
|
|
40
|
+
deniedMessage: MEMORY_CREATE_DENIED_MESSAGE,
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("a server-composed request asks nothing", () => {
|
|
46
|
+
expect(
|
|
47
|
+
resolveMemoryCreateTargets(
|
|
48
|
+
ctxFor(testCallerIdentity({ callerClass: "internal" })),
|
|
49
|
+
),
|
|
50
|
+
).toEqual([]);
|
|
51
|
+
expect(
|
|
52
|
+
resolveMemoryCreateTargets(
|
|
53
|
+
ctxFor(
|
|
54
|
+
testCallerIdentity({ callerClass: "user", origin: "in-process" }),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -77,6 +77,7 @@ import { newPipeline } from "../../pipeline/pipeline.js";
|
|
|
77
77
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
78
78
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
79
79
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
80
|
+
import { newAuthorizeResolvedTargetStep } from "../../pipeline/steps/authorize-resolved-target.js";
|
|
80
81
|
import { newGuardMemoryCaptureStep } from "../../pipeline/steps/guard-memory-capture.js";
|
|
81
82
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
82
83
|
import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
|
|
@@ -118,6 +119,7 @@ import {
|
|
|
118
119
|
newResolveMemoryDefaultsStep,
|
|
119
120
|
newTransitionMemoryLifecycleStep,
|
|
120
121
|
newValidateMemoryUpdateStep,
|
|
122
|
+
resolveMemoryCreateTargets,
|
|
121
123
|
} from "./steps.js";
|
|
122
124
|
|
|
123
125
|
export interface MemoryControllerDeps {
|
|
@@ -194,6 +196,15 @@ async function createMemory(
|
|
|
194
196
|
.addStep(newGuardMemoryCaptureStep(deps.runnerCredentialProvider))
|
|
195
197
|
.addStep(newValidateVisibilityStep())
|
|
196
198
|
.addStep(newResolveMemoryDefaultsStep())
|
|
199
|
+
// Before the enablement and cap checks, so a refused caller learns
|
|
200
|
+
// nothing about the organization's settings (steps.ts,
|
|
201
|
+
// resolveMemoryCreateTargets).
|
|
202
|
+
.addStep(
|
|
203
|
+
newAuthorizeResolvedTargetStep(
|
|
204
|
+
deps.authorizer,
|
|
205
|
+
resolveMemoryCreateTargets,
|
|
206
|
+
),
|
|
207
|
+
)
|
|
197
208
|
.addStep(newCheckMemoryEnablementStep(deps.store))
|
|
198
209
|
.addStep(newCheckMemoryCapStep(deps.store))
|
|
199
210
|
.addStep(newResolveSlugStep())
|
|
@@ -30,6 +30,7 @@ import { MemoryListSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/i
|
|
|
30
30
|
import { MemoryProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/spec_pb";
|
|
31
31
|
import { MemoryStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/status_pb";
|
|
32
32
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
33
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
33
34
|
import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
34
35
|
|
|
35
36
|
import {
|
|
@@ -42,6 +43,8 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
42
43
|
import type { ListReadScope } from "../../extensions/list-read-scope.js";
|
|
43
44
|
import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
|
|
44
45
|
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
46
|
+
import { isServerComposedRequest } from "../../extensions/identity.js";
|
|
47
|
+
import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
|
|
45
48
|
import {
|
|
46
49
|
generateId,
|
|
47
50
|
setAuditFieldsForUpdate,
|
|
@@ -159,6 +162,44 @@ export function newResolveMemoryDefaultsStep(): PipelineStep<
|
|
|
159
162
|
};
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
/** The deny copy of the create lane's organization bar. */
|
|
166
|
+
export const MEMORY_CREATE_DENIED_MESSAGE =
|
|
167
|
+
"unauthorized to capture memory in this organization";
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The create lane's authorization question, for AuthorizeResolvedTarget
|
|
171
|
+
* after ResolveMemoryDefaults (which requires metadata.org) and BEFORE
|
|
172
|
+
* CheckMemoryEnablement, so a refused caller learns nothing about the
|
|
173
|
+
* organization's memory settings. The RPC is is_skip_authorization
|
|
174
|
+
* because the row does not exist yet and the subject IS the caller
|
|
175
|
+
* (command.proto); GuardMemoryCapture is the gate that decides WHO may
|
|
176
|
+
* capture at all, and this question is WHERE: can_create_session on
|
|
177
|
+
* metadata.org — memory is captured inside a conversation, so whoever may
|
|
178
|
+
* converse in an organization may remember there.
|
|
179
|
+
*
|
|
180
|
+
* The credentialed capture lane is already scoped to the run's
|
|
181
|
+
* organization by the credential provider (a mismatch is refused inside
|
|
182
|
+
* GuardMemoryCapture with its own copy); this question closes the plain
|
|
183
|
+
* path, a person with no run credential naming an organization they hold
|
|
184
|
+
* nothing in. A server-composed traversal asks nothing, as the gate itself
|
|
185
|
+
* says: the entry-point request already passed.
|
|
186
|
+
*/
|
|
187
|
+
export function resolveMemoryCreateTargets(
|
|
188
|
+
ctx: RequestContext<typeof MemorySchema>,
|
|
189
|
+
): ReadonlyArray<AuthorizationTarget> {
|
|
190
|
+
if (isServerComposedRequest(ctx.callerIdentity)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
permission: IamPermission.can_create_session,
|
|
196
|
+
resourceKind: ApiResourceKind.organization,
|
|
197
|
+
resourceId: ctx.newState.metadata?.org ?? "",
|
|
198
|
+
deniedMessage: MEMORY_CREATE_DENIED_MESSAGE,
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
162
203
|
/**
|
|
163
204
|
* CheckMemoryEnablement — Go checkMemoryEnablementStep: enforces the
|
|
164
205
|
* org's memory_enabled switch at write time, FAIL-CLOSED (DD-005 D2): a
|
|
@@ -52,6 +52,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
52
52
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
53
53
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
54
54
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
55
|
+
import {
|
|
56
|
+
loadedTargetAsMethod,
|
|
57
|
+
newAuthorizeResolvedTargetStep,
|
|
58
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
55
59
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
56
60
|
import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
|
|
57
61
|
import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
|
|
@@ -362,6 +366,12 @@ async function getByReference(
|
|
|
362
366
|
)
|
|
363
367
|
.addStep(newValidateProtoStep())
|
|
364
368
|
.addStep(newLoadByReferenceStep(deps.store, OAuthAppSchema))
|
|
369
|
+
.addStep(
|
|
370
|
+
newAuthorizeResolvedTargetStep(
|
|
371
|
+
deps.authorizer,
|
|
372
|
+
loadedTargetAsMethod(OAuthAppQueryController.method.get),
|
|
373
|
+
),
|
|
374
|
+
)
|
|
365
375
|
.build()
|
|
366
376
|
.execute(reqCtx);
|
|
367
377
|
const app = reqCtx.get(TARGET_RESOURCE_KEY) as OAuthApp;
|