@stigmer/server 3.15.3-dev.20260916211208 → 3.16.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/lane-admission.d.ts +50 -0
- package/dist/authorization/lane-admission.d.ts.map +1 -0
- package/dist/authorization/lane-admission.js +13 -0
- package/dist/authorization/lane-admission.js.map +1 -0
- package/dist/authorization/schedule-fire-caller.d.ts +2 -2
- package/dist/authorization/schedule-fire-caller.d.ts.map +1 -1
- package/dist/authorization/schedule-fire-caller.js +8 -8
- package/dist/authorization/schedule-fire-caller.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +66 -14
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +6 -1
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/executioncontext/resolve-values-for-caller.d.ts +33 -14
- package/dist/domain/executioncontext/resolve-values-for-caller.d.ts.map +1 -1
- package/dist/domain/executioncontext/resolve-values-for-caller.js +32 -8
- package/dist/domain/executioncontext/resolve-values-for-caller.js.map +1 -1
- package/dist/domain/identityaccount/actor.d.ts +33 -18
- package/dist/domain/identityaccount/actor.d.ts.map +1 -1
- package/dist/domain/identityaccount/actor.js +20 -3
- package/dist/domain/identityaccount/actor.js.map +1 -1
- package/dist/domain/identityaccount/resolve.d.ts +19 -0
- package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
- package/dist/domain/identityaccount/resolve.js +12 -0
- package/dist/domain/identityaccount/resolve.js.map +1 -1
- package/dist/domain/mcpserver/connect-execution-id.d.ts +5 -0
- package/dist/domain/mcpserver/connect-execution-id.d.ts.map +1 -0
- package/dist/domain/mcpserver/connect-execution-id.js +32 -0
- package/dist/domain/mcpserver/connect-execution-id.js.map +1 -0
- package/dist/domain/mcpserver/connect.d.ts +28 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +27 -7
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/engine.d.ts +9 -3
- package/dist/domain/mcpserver/engine.d.ts.map +1 -1
- package/dist/domain/mcpserver/engine.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +1 -1
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/platform/controller.d.ts +24 -11
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.d.ts.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +4 -2
- package/dist/domain/plugin/overlay/sanitize.js.map +1 -1
- package/dist/domain/workflowexecution/phases.d.ts +21 -0
- package/dist/domain/workflowexecution/phases.d.ts.map +1 -0
- package/dist/domain/workflowexecution/phases.js +31 -0
- package/dist/domain/workflowexecution/phases.js.map +1 -0
- package/dist/identity/oidc-verifier.js +8 -1
- package/dist/identity/oidc-verifier.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +15 -0
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +34 -0
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +5 -3
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/runnerauth/bound-execution.d.ts +27 -0
- package/dist/runnerauth/bound-execution.d.ts.map +1 -0
- package/dist/runnerauth/bound-execution.js +137 -0
- package/dist/runnerauth/bound-execution.js.map +1 -0
- package/dist/runnerauth/built-in-runner-credential-provider.d.ts +28 -0
- package/dist/runnerauth/built-in-runner-credential-provider.d.ts.map +1 -0
- package/dist/runnerauth/built-in-runner-credential-provider.js +217 -0
- package/dist/runnerauth/built-in-runner-credential-provider.js.map +1 -0
- package/dist/runnerauth/constants.d.ts +63 -0
- package/dist/runnerauth/constants.d.ts.map +1 -0
- package/dist/runnerauth/constants.js +63 -0
- package/dist/runnerauth/constants.js.map +1 -0
- package/dist/runnerauth/dispatch-credential.d.ts +38 -0
- package/dist/runnerauth/dispatch-credential.d.ts.map +1 -0
- package/dist/runnerauth/dispatch-credential.js +17 -0
- package/dist/runnerauth/dispatch-credential.js.map +1 -0
- package/dist/runnerauth/runner-credential-provider.d.ts +49 -11
- package/dist/runnerauth/runner-credential-provider.d.ts.map +1 -1
- package/dist/runnerauth/runner-credential-provider.js +16 -4
- package/dist/runnerauth/runner-credential-provider.js.map +1 -1
- package/dist/runnerauth/runner-subject-verifier.d.ts +14 -0
- package/dist/runnerauth/runner-subject-verifier.d.ts.map +1 -0
- package/dist/runnerauth/runner-subject-verifier.js +115 -0
- package/dist/runnerauth/runner-subject-verifier.js.map +1 -0
- package/dist/runnerauth/runnerauth.d.ts +33 -4
- package/dist/runnerauth/runnerauth.d.ts.map +1 -1
- package/dist/runnerauth/runnerauth.js +131 -35
- package/dist/runnerauth/runnerauth.js.map +1 -1
- package/dist/temporal/agentexecution/engine-client.d.ts +13 -0
- package/dist/temporal/agentexecution/engine-client.d.ts.map +1 -1
- package/dist/temporal/agentexecution/engine-client.js +10 -2
- package/dist/temporal/agentexecution/engine-client.js.map +1 -1
- package/dist/temporal/agentexecution/workflow-input.d.ts +13 -0
- package/dist/temporal/agentexecution/workflow-input.d.ts.map +1 -1
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.d.ts.map +1 -1
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js +35 -31
- package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js.map +1 -1
- package/dist/temporal/workflowexecution/engine-client.d.ts +24 -0
- package/dist/temporal/workflowexecution/engine-client.d.ts.map +1 -1
- package/dist/temporal/workflowexecution/engine-client.js +26 -19
- package/dist/temporal/workflowexecution/engine-client.js.map +1 -1
- package/dist/temporal/workflowexecution/workflow-input.d.ts +14 -0
- package/dist/temporal/workflowexecution/workflow-input.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/authorization/__tests__/lane-admission.test.ts +115 -0
- package/src/authorization/lane-admission.ts +73 -0
- package/src/authorization/schedule-fire-caller.ts +10 -14
- package/src/boot/compose.ts +79 -22
- package/src/boot/inprocess.ts +7 -1
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +104 -0
- package/src/domain/executioncontext/__tests__/resolve-values-capability.test.ts +33 -4
- package/src/domain/executioncontext/resolve-values-for-caller.ts +79 -22
- package/src/domain/identityaccount/__tests__/account-for-stamp.test.ts +111 -0
- package/src/domain/identityaccount/actor.ts +61 -21
- package/src/domain/identityaccount/resolve.ts +31 -0
- package/src/domain/mcpserver/__tests__/connect.test.ts +17 -2
- package/src/domain/mcpserver/connect-execution-id.ts +34 -0
- package/src/domain/mcpserver/connect.ts +38 -3
- package/src/domain/mcpserver/engine.ts +9 -3
- package/src/domain/mcpserver/start-connect.ts +1 -1
- package/src/domain/platform/controller.ts +24 -11
- package/src/domain/plugin/__tests__/plugin.test.ts +116 -1
- package/src/domain/plugin/__tests__/sanitize.test.ts +2 -2
- package/src/domain/plugin/overlay/sanitize.ts +4 -2
- package/src/domain/workflowexecution/__tests__/phases.test.ts +58 -0
- package/src/domain/workflowexecution/phases.ts +33 -0
- package/src/extensions/__tests__/extension-composition.test.ts +7 -1
- package/src/identity/__tests__/oidc-verifier.test.ts +22 -0
- package/src/identity/oidc-verifier.ts +8 -1
- package/src/pipeline/__tests__/kind-by-id-prefix.test.ts +75 -0
- package/src/pipeline/apiresource-meta.ts +41 -0
- package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +55 -35
- package/src/pipeline/steps/guard-memory-capture.ts +5 -3
- package/src/runnerauth/__tests__/bound-execution.test.ts +334 -0
- package/src/runnerauth/__tests__/built-in-runner-credential-provider.test.ts +516 -0
- package/src/runnerauth/__tests__/dispatch-credential.test.ts +94 -0
- package/src/runnerauth/__tests__/runner-credential-provider.test.ts +27 -0
- package/src/runnerauth/__tests__/runner-subject-composed.test.ts +497 -0
- package/src/runnerauth/__tests__/runner-subject-verifier.test.ts +541 -0
- package/src/runnerauth/bound-execution.ts +270 -0
- package/src/runnerauth/built-in-runner-credential-provider.ts +300 -0
- package/src/runnerauth/constants.ts +74 -0
- package/src/runnerauth/dispatch-credential.ts +63 -0
- package/src/runnerauth/runner-credential-provider.ts +60 -11
- package/src/runnerauth/runner-subject-verifier.ts +152 -0
- package/src/runnerauth/runnerauth.ts +148 -38
- package/src/temporal/agentexecution/__tests__/engine-client.test.ts +107 -38
- package/src/temporal/agentexecution/__tests__/invoke-workflow.test.ts +105 -2
- package/src/temporal/agentexecution/engine-client.ts +27 -2
- package/src/temporal/agentexecution/workflow-input.ts +13 -0
- package/src/temporal/agentexecution/workflows/invoke-agent-execution.ts +90 -44
- package/src/temporal/workflowexecution/__tests__/engine-client.test.ts +139 -33
- package/src/temporal/workflowexecution/engine-client.ts +48 -21
- package/src/temporal/workflowexecution/workflow-input.ts +14 -0
|
@@ -14,21 +14,37 @@
|
|
|
14
14
|
* distinguishes itself with an execution-scoped token minted by
|
|
15
15
|
* getRunnerScopedToken and presented as a Bearer authorization header
|
|
16
16
|
* (the same header shape a cloud runner uses for its sandbox credential).
|
|
17
|
-
* Decrypt requires the FULL binding: a valid
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* Decrypt requires the FULL binding: a valid token whose execution_id
|
|
18
|
+
* claim equals this EC's spec.execution_id, and — for a RUN credential,
|
|
19
|
+
* the no-`exp` token the dispatch path hands the runner — a bound
|
|
20
|
+
* execution that is still live (runnerauth/bound-execution.ts: not
|
|
21
|
+
* terminal, or ended within the grace). Everything else — no header, a
|
|
22
|
+
* malformed or expired token, a token minted for a different execution,
|
|
23
|
+
* a run credential whose run is over — falls closed to the same
|
|
24
|
+
* redaction get/getByReference apply, as a SUCCESSFUL response, not an
|
|
25
|
+
* error.
|
|
22
26
|
*
|
|
23
|
-
*
|
|
24
|
-
* chassis
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* The decrypt decision lives HERE, in the domain, whatever the identity
|
|
28
|
+
* chassis makes of the token. Under trusted-local no verifier claims it
|
|
29
|
+
* and this is the one RPC that reads the raw header (the consumer the
|
|
30
|
+
* runnerauth module header reserves, "the executioncontext resolve
|
|
31
|
+
* step"). Under the built-in authorization posture the runner-subject
|
|
32
|
+
* verifier ALSO admits the same token as the run's human at position 1
|
|
33
|
+
* (runnerauth.ts header, 2026-09-16) — that decides WHO is calling and
|
|
34
|
+
* whether they may read the row; whether the row's secrets are decrypted
|
|
35
|
+
* for them is still this lane's binding check, so a person who can view
|
|
36
|
+
* an execution's context never receives its plaintext by holding a
|
|
37
|
+
* credential for a different one. The redaction-as-success contract is
|
|
38
|
+
* pinned by the conformance suites and holds on every arm.
|
|
39
|
+
*
|
|
40
|
+
* Why liveness is read only for a no-`exp` token: a clocked token's
|
|
41
|
+
* validity was decided by `verify` (its clock) and its binding, and the
|
|
42
|
+
* ExecutionContext it opens need not name an execution row at all — the
|
|
43
|
+
* connect lane's token opens an EC for an MCP discovery, and the rosters
|
|
44
|
+
* pin a clocked token decrypting an EC whose execution id names no row.
|
|
45
|
+
* A run credential has no clock; the row is the only thing that can end
|
|
46
|
+
* it, and a plaintext token in Temporal history must not decrypt a
|
|
47
|
+
* finished run's secrets for good.
|
|
32
48
|
*
|
|
33
49
|
* # Decrypt error doctrine (the oss#405 runtime-resolution doctrine,
|
|
34
50
|
* # arms per the two-armed taxonomy in encryption/errors.ts)
|
|
@@ -56,8 +72,16 @@ import { EncryptionUnavailableError } from "../../encryption/encryption.js";
|
|
|
56
72
|
import type { SecretService } from "../../encryption/encryption.js";
|
|
57
73
|
import { internalError } from "../../pipeline/errors.js";
|
|
58
74
|
import { parseBearerToken } from "../../pipeline/interceptors/auth.js";
|
|
75
|
+
import {
|
|
76
|
+
bindsARun,
|
|
77
|
+
loadBoundExecution,
|
|
78
|
+
} from "../../runnerauth/bound-execution.js";
|
|
79
|
+
import type { BoundExecutionStore } from "../../runnerauth/bound-execution.js";
|
|
59
80
|
import type { RunnerCredentialProvider } from "../../runnerauth/runner-credential-provider.js";
|
|
60
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
isClockedToken,
|
|
83
|
+
TOKEN_TYPE_EXECUTION_SCOPED,
|
|
84
|
+
} from "../../runnerauth/runnerauth.js";
|
|
61
85
|
import { encryptionKeyMissingMessage } from "./constants.js";
|
|
62
86
|
import { redactExecutionContextSecrets } from "./redact.js";
|
|
63
87
|
|
|
@@ -65,6 +89,8 @@ export interface ResolveValuesDeps {
|
|
|
65
89
|
readonly logger: Logger;
|
|
66
90
|
readonly secretService: SecretService;
|
|
67
91
|
readonly runnerAuthService: RunnerCredentialProvider;
|
|
92
|
+
/** Where the bound execution lives — read for a run credential's liveness only. */
|
|
93
|
+
readonly store: BoundExecutionStore;
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
/**
|
|
@@ -123,15 +149,23 @@ async function runnerMayDecrypt(
|
|
|
123
149
|
}
|
|
124
150
|
}
|
|
125
151
|
|
|
126
|
-
const
|
|
127
|
-
|
|
152
|
+
const token = bearerToken(ctx);
|
|
153
|
+
const executionId = verifyRunnerToken(deps, token, ec);
|
|
154
|
+
if (executionId === undefined) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (!isClockedToken(token) && !(await runIsLive(deps, executionId))) {
|
|
128
158
|
deps.logger.debug(
|
|
129
|
-
"
|
|
159
|
+
"Run credential presented for a run that is over - redacting execution context secrets",
|
|
130
160
|
{ executionId },
|
|
131
161
|
);
|
|
132
|
-
return
|
|
162
|
+
return false;
|
|
133
163
|
}
|
|
134
|
-
|
|
164
|
+
deps.logger.debug(
|
|
165
|
+
"Scope-bound runner token presented - decrypting execution context secrets",
|
|
166
|
+
{ executionId },
|
|
167
|
+
);
|
|
168
|
+
return true;
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
/**
|
|
@@ -148,10 +182,9 @@ async function runnerMayDecrypt(
|
|
|
148
182
|
*/
|
|
149
183
|
function verifyRunnerToken(
|
|
150
184
|
deps: ResolveValuesDeps,
|
|
151
|
-
|
|
185
|
+
token: string,
|
|
152
186
|
ec: ExecutionContext,
|
|
153
187
|
): string | undefined {
|
|
154
|
-
const token = bearerToken(ctx);
|
|
155
188
|
if (token === "") {
|
|
156
189
|
return undefined;
|
|
157
190
|
}
|
|
@@ -183,6 +216,30 @@ function verifyRunnerToken(
|
|
|
183
216
|
return tokenExecutionId;
|
|
184
217
|
}
|
|
185
218
|
|
|
219
|
+
/**
|
|
220
|
+
* A run credential's liveness: the bound execution exists, IS a run
|
|
221
|
+
* (`bindsARun` — a clockless token bound to a connect is a shape no mint
|
|
222
|
+
* produces, refused here and by the verifier through the one predicate)
|
|
223
|
+
* and is live (runnerauth/bound-execution.ts). A missing row is "not
|
|
224
|
+
* live" — the credential opens nothing. A store fault is an
|
|
225
|
+
* infrastructure fault, sanitized here as every direct handler does (the
|
|
226
|
+
* store-fault doctrine): an outage must not read as a redaction decision,
|
|
227
|
+
* and the same store just served the row this read is for.
|
|
228
|
+
*/
|
|
229
|
+
async function runIsLive(
|
|
230
|
+
deps: ResolveValuesDeps,
|
|
231
|
+
executionId: string,
|
|
232
|
+
): Promise<boolean> {
|
|
233
|
+
try {
|
|
234
|
+
const execution = await loadBoundExecution(deps.store, executionId);
|
|
235
|
+
return (
|
|
236
|
+
execution !== undefined && bindsARun(execution.kind) && execution.live
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw internalError(error, "failed to load the run credential's execution");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
186
243
|
/**
|
|
187
244
|
* The Bearer credential from the request's authorization header; empty
|
|
188
245
|
* when absent or differently shaped. The parsing shape (Go's exact
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins `accountForStamp` (resolve.ts): the account a row's CREATOR STAMP
|
|
3
|
+
* names, read the two ways a stamp has been written — as an account id
|
|
4
|
+
* (rows stamped since 3.15.0) and as the raw issuer subject (rows the
|
|
5
|
+
* 3.14.x verifiers stamped) — stated ONCE so the schedule fire caller and
|
|
6
|
+
* the runner-subject verifier, which both make a server lane act as the
|
|
7
|
+
* person a row names, cannot resolve the same stamp two ways. The read
|
|
8
|
+
* order is `accountForCaller`'s: by id first, then the direct subject
|
|
9
|
+
* lookup.
|
|
10
|
+
*
|
|
11
|
+
* - an account-id stamp resolves by that id;
|
|
12
|
+
* - a raw-subject stamp resolves through the direct lookup;
|
|
13
|
+
* - the empty stamp is `undefined` with NO read — the store is never
|
|
14
|
+
* asked about "";
|
|
15
|
+
* - a stamp naming nobody (the laptop's `"system"`, a trusted-local
|
|
16
|
+
* email, a deleted account) is `undefined` — the CALLER decides what
|
|
17
|
+
* that means (the fire caller's deterministic refusal; the verifier's
|
|
18
|
+
* liveness sentence); this function never throws for it;
|
|
19
|
+
* - a store fault propagates as the same error object.
|
|
20
|
+
*
|
|
21
|
+
* Written failing on 2026-09-16, before the export exists; the change that follows
|
|
22
|
+
* extracts it from the fire caller's inline read (extract, do not copy).
|
|
23
|
+
*/
|
|
24
|
+
import { create } from "@bufbuild/protobuf";
|
|
25
|
+
import { describe, expect, it, vi } from "vitest";
|
|
26
|
+
|
|
27
|
+
import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
28
|
+
import { IdentityAccountProvisioningMode } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/enum_pb";
|
|
29
|
+
|
|
30
|
+
import { accountIdFor } from "../constants.js";
|
|
31
|
+
import { accountForStamp } from "../resolve.js";
|
|
32
|
+
import type { AccountsByCaller } from "../resolve.js";
|
|
33
|
+
import { fakeIdentityAccountStore } from "./support.js";
|
|
34
|
+
|
|
35
|
+
const SUBJECT = "auth0|carol";
|
|
36
|
+
const CAROL = accountIdFor(SUBJECT);
|
|
37
|
+
|
|
38
|
+
function seeded() {
|
|
39
|
+
const accounts = fakeIdentityAccountStore();
|
|
40
|
+
accounts.rows.set(
|
|
41
|
+
CAROL,
|
|
42
|
+
create(IdentityAccountSchema, {
|
|
43
|
+
metadata: { id: CAROL, name: "Carol Danvers" },
|
|
44
|
+
spec: {
|
|
45
|
+
idpId: SUBJECT,
|
|
46
|
+
email: "carol@example.com",
|
|
47
|
+
provisioningMode: IdentityAccountProvisioningMode.direct,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
return accounts;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("accountForStamp", () => {
|
|
55
|
+
it("an account-id stamp resolves by that id", async () => {
|
|
56
|
+
const account = await accountForStamp(seeded(), CAROL);
|
|
57
|
+
expect(account?.metadata?.id).toBe(CAROL);
|
|
58
|
+
expect(account?.spec?.email).toBe("carol@example.com");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("a raw-subject stamp (the 3.14.x shape) resolves through the direct lookup to the same account", async () => {
|
|
62
|
+
expect((await accountForStamp(seeded(), SUBJECT))?.metadata?.id).toBe(
|
|
63
|
+
CAROL,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('the empty stamp is undefined with NO read — the store is never asked about ""', async () => {
|
|
68
|
+
const findById = vi.fn(async () => undefined);
|
|
69
|
+
const findDirectByIdpId = vi.fn(async () => undefined);
|
|
70
|
+
const accounts: AccountsByCaller = { findById, findDirectByIdpId };
|
|
71
|
+
expect(await accountForStamp(accounts, "")).toBeUndefined();
|
|
72
|
+
expect(findById).not.toHaveBeenCalled();
|
|
73
|
+
expect(findDirectByIdpId).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it.each([
|
|
77
|
+
["the laptop's placeholder", "system"],
|
|
78
|
+
["a trusted-local email stamp", "operator@example.com"],
|
|
79
|
+
["a deleted account", accountIdFor("auth0|gone")],
|
|
80
|
+
])(
|
|
81
|
+
"%s is undefined — the caller decides what nobody means",
|
|
82
|
+
async (_label, stamp) => {
|
|
83
|
+
expect(await accountForStamp(seeded(), stamp)).toBeUndefined();
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
it("reads by id first, then by subject — two primary-key reads at most, in accountForCaller's order", async () => {
|
|
88
|
+
const calls: string[] = [];
|
|
89
|
+
const accounts: AccountsByCaller = {
|
|
90
|
+
findById: async (id) => {
|
|
91
|
+
calls.push(`id:${id}`);
|
|
92
|
+
return undefined;
|
|
93
|
+
},
|
|
94
|
+
findDirectByIdpId: async (idpId) => {
|
|
95
|
+
calls.push(`sub:${idpId}`);
|
|
96
|
+
return undefined;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
await accountForStamp(accounts, SUBJECT);
|
|
100
|
+
expect(calls).toEqual([`id:${SUBJECT}`, `sub:${SUBJECT}`]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("a store fault propagates as the same error object, never a credential rejection", async () => {
|
|
104
|
+
const fault = new Error("connection reset");
|
|
105
|
+
const accounts: AccountsByCaller = {
|
|
106
|
+
findById: () => Promise.reject(fault),
|
|
107
|
+
findDirectByIdpId: async () => undefined,
|
|
108
|
+
};
|
|
109
|
+
await expect(accountForStamp(accounts, CAROL)).rejects.toBe(fault);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -1,30 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* An account acting as itself — the ONE
|
|
3
|
-
* identity
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* An account acting as itself — the ONE place server code builds the
|
|
3
|
+
* caller identity for a person it has resolved from a row rather than
|
|
4
|
+
* from the token that person presented. Two constructions, one shape:
|
|
5
|
+
* the account id is the principal, so the audit actor on every write
|
|
6
|
+
* names the account the way every other write names it, and email and
|
|
7
|
+
* display name ride along when the row knows them (the trusted-local
|
|
8
|
+
* identity's shape, pipeline/interceptors/auth.ts).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* `accountAsCaller(account)` — the IN-PROCESS lane: no issuer, no token,
|
|
11
|
+
* class `user`. Two consumers: the trusted-local boot ensure, which
|
|
12
|
+
* grants the operator ownership AS their account
|
|
13
|
+
* (domain/iampolicy/membership.ts), and the built-in schedule fire
|
|
14
|
+
* caller, which makes a fire act as the schedule's creator
|
|
15
|
+
* (authorization/schedule-fire-caller.ts). Both ride the in-process
|
|
16
|
+
* transport, whose interceptor stamps `origin: "in-process"` on top —
|
|
17
|
+
* the transport-trust marker is never set here.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* `accountAsRunnerCaller(account, rawToken)` — the WIRE lane: the
|
|
20
|
+
* runner-subject verifier (runnerauth/runner-subject-verifier.ts) admits
|
|
21
|
+
* the bearer of a run credential as the human whose run it is. Class
|
|
22
|
+
* `runner`, because the Authorizer's lane admission and the memory
|
|
23
|
+
* capture gate must tell a runner acting as a person from the person
|
|
24
|
+
* (extensions/identity.ts names the class for exactly this), and the
|
|
25
|
+
* verified token carried, because the provider's capabilities re-read the
|
|
26
|
+
* binding off `rawToken` (the cloud's own pattern) — one HMAC, no store
|
|
27
|
+
* read. No issuer: the server signed it.
|
|
28
|
+
*
|
|
29
|
+
* Why not a token for the in-process lane: open source mints no
|
|
30
|
+
* credential for a person the server is composing a request for; the
|
|
31
|
+
* identity is what the propagation header carries, and every consumer
|
|
32
|
+
* that reads a subject off the raw token (`idpIdOf`) is reached only
|
|
33
|
+
* through `accountForCaller`, which resolves this identity by its id
|
|
34
|
+
* first and never asks the token. The same holds for the runner lane:
|
|
35
|
+
* our token carries no `sub`, and nothing downstream needs one.
|
|
22
36
|
*/
|
|
23
37
|
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
24
38
|
|
|
25
39
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
26
40
|
|
|
27
41
|
export function accountAsCaller(account: IdentityAccount): CallerIdentity {
|
|
42
|
+
return {
|
|
43
|
+
...principalOf(account),
|
|
44
|
+
callerClass: "user",
|
|
45
|
+
issuer: "",
|
|
46
|
+
rawToken: "",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function accountAsRunnerCaller(
|
|
51
|
+
account: IdentityAccount,
|
|
52
|
+
rawToken: string,
|
|
53
|
+
): CallerIdentity {
|
|
54
|
+
if (rawToken === "") {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"a runner caller carries the credential it presented — refusing to build one with none",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...principalOf(account),
|
|
61
|
+
callerClass: "runner",
|
|
62
|
+
issuer: "",
|
|
63
|
+
rawToken,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The account id as principal, with the display identity the row knows. */
|
|
68
|
+
function principalOf(
|
|
69
|
+
account: IdentityAccount,
|
|
70
|
+
): Pick<CallerIdentity, "identityId" | "email" | "displayName"> {
|
|
28
71
|
const accountId = account.metadata?.id ?? "";
|
|
29
72
|
if (accountId === "") {
|
|
30
73
|
throw new Error(
|
|
@@ -35,9 +78,6 @@ export function accountAsCaller(account: IdentityAccount): CallerIdentity {
|
|
|
35
78
|
const displayName = account.metadata?.name ?? "";
|
|
36
79
|
return {
|
|
37
80
|
identityId: accountId,
|
|
38
|
-
callerClass: "user",
|
|
39
|
-
issuer: "",
|
|
40
|
-
rawToken: "",
|
|
41
81
|
...(email !== "" ? { email } : {}),
|
|
42
82
|
...(displayName !== "" ? { displayName } : {}),
|
|
43
83
|
};
|
|
@@ -41,6 +41,19 @@
|
|
|
41
41
|
* cannot answer the same question two ways. A credential naming no
|
|
42
42
|
* subject is `undefined` with no second read: the store is never asked
|
|
43
43
|
* about "".
|
|
44
|
+
*
|
|
45
|
+
* The third reading is a ROW's creator stamp, `accountForStamp`: the
|
|
46
|
+
* account a `created_by.id` names, read the two ways a stamp has been
|
|
47
|
+
* written — as an account id (rows stamped since 3.15.0) and as the raw
|
|
48
|
+
* issuer subject (rows the 3.14.x verifiers stamped) — in
|
|
49
|
+
* `accountForCaller`'s order. Two lanes make the server act as the
|
|
50
|
+
* person a row names: the built-in schedule fire caller (a fire acts as
|
|
51
|
+
* the schedule's creator) and the runner-subject verifier (a run
|
|
52
|
+
* credential admits its bearer as the execution's creator). Stated here
|
|
53
|
+
* so they cannot resolve one stamp two ways; each decides for itself
|
|
54
|
+
* what "nobody" means (the fire caller's deterministic refusal, the
|
|
55
|
+
* verifier's liveness sentence), so this function answers `undefined`
|
|
56
|
+
* and never throws for it. The empty stamp is `undefined` with no read.
|
|
44
57
|
*/
|
|
45
58
|
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
46
59
|
|
|
@@ -93,3 +106,21 @@ export async function accountForCaller(
|
|
|
93
106
|
}
|
|
94
107
|
return accounts.findDirectByIdpId(subject);
|
|
95
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The account a row's creator stamp names, or `undefined` when it names
|
|
112
|
+
* nobody (the laptop's `"system"`, a trusted-local email, a deleted
|
|
113
|
+
* account, the empty stamp). Faults propagate as they are.
|
|
114
|
+
*/
|
|
115
|
+
export async function accountForStamp(
|
|
116
|
+
accounts: AccountsByCaller,
|
|
117
|
+
stamp: string,
|
|
118
|
+
): Promise<IdentityAccount | undefined> {
|
|
119
|
+
if (stamp === "") {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return (
|
|
123
|
+
(await accounts.findById(stamp)) ??
|
|
124
|
+
(await accounts.findDirectByIdpId(stamp))
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -28,6 +28,7 @@ import { ExecutionContextSchema } from "@stigmer/protos/ai/stigmer/agentic/execu
|
|
|
28
28
|
|
|
29
29
|
import { createLogger } from "../../../boot/logger.js";
|
|
30
30
|
import { SecretService } from "../../../encryption/encryption.js";
|
|
31
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
31
32
|
import { newExecutionScopedRunnerCredentialProvider } from "../../../runnerauth/runner-credential-provider.js";
|
|
32
33
|
import { RunnerAuthService } from "../../../runnerauth/runnerauth.js";
|
|
33
34
|
import { SqliteStore } from "../../../store/sqlite/store.js";
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
startBestEffortConnect,
|
|
42
43
|
} from "../connect.js";
|
|
43
44
|
import type { McpServerConnectDeps } from "../connect.js";
|
|
45
|
+
import { isConnectExecutionId } from "../connect-execution-id.js";
|
|
44
46
|
import { ManagedEnvironmentService } from "../oauth/managed-env.js";
|
|
45
47
|
import {
|
|
46
48
|
RUNNER_QUEUE_WARNING,
|
|
@@ -115,6 +117,8 @@ interface Harness {
|
|
|
115
117
|
deps: McpServerConnectDeps;
|
|
116
118
|
engine: FakeEngine;
|
|
117
119
|
ecCreates: number;
|
|
120
|
+
/** The caller each EC create was handed — the connect's person, never the server (the mcp-connect binding's stamp). */
|
|
121
|
+
ecCreators: CallerIdentity[];
|
|
118
122
|
ecDeletes: string[];
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -150,6 +154,7 @@ function makeHarness(options: FakeEngineOptions = {}): Harness {
|
|
|
150
154
|
const harness: Harness = {
|
|
151
155
|
engine,
|
|
152
156
|
ecCreates: 0,
|
|
157
|
+
ecCreators: [],
|
|
153
158
|
ecDeletes: [],
|
|
154
159
|
deps: {
|
|
155
160
|
store,
|
|
@@ -165,8 +170,9 @@ function makeHarness(options: FakeEngineOptions = {}): Harness {
|
|
|
165
170
|
},
|
|
166
171
|
},
|
|
167
172
|
executionContext: {
|
|
168
|
-
create: async () => {
|
|
173
|
+
create: async (_ec, caller) => {
|
|
169
174
|
harness.ecCreates += 1;
|
|
175
|
+
harness.ecCreators.push(caller);
|
|
170
176
|
return create(ExecutionContextSchema, {
|
|
171
177
|
metadata: { id: `ectx_${harness.ecCreates}` },
|
|
172
178
|
});
|
|
@@ -356,7 +362,7 @@ describe("connect (blocking lane)", () => {
|
|
|
356
362
|
expect(second.status?.toolApprovals[0]?.toolName).toBe("search");
|
|
357
363
|
});
|
|
358
364
|
|
|
359
|
-
it("creates the ephemeral EC from runtime_env, mints the decrypt token, and deletes the EC after settle", async () => {
|
|
365
|
+
it("creates the ephemeral EC from runtime_env AS THE CALLER, mints the decrypt token, and deletes the EC after settle", async () => {
|
|
360
366
|
const harness = makeHarness();
|
|
361
367
|
const server = await seedServer();
|
|
362
368
|
await connect(
|
|
@@ -366,9 +372,16 @@ describe("connect (blocking lane)", () => {
|
|
|
366
372
|
}),
|
|
367
373
|
);
|
|
368
374
|
expect(harness.ecCreates).toBe(1);
|
|
375
|
+
// The row's creator stamp is the connect token's person under the
|
|
376
|
+
// built-in posture (runnerauth/bound-execution.ts), so the connect
|
|
377
|
+
// hands the client the caller, never the server's own identity.
|
|
378
|
+
expect(harness.ecCreators).toEqual([testCaller]);
|
|
369
379
|
expect(harness.ecDeletes).toEqual(["ectx_1"]);
|
|
370
380
|
const input = harness.engine.startedInputs[0];
|
|
371
381
|
expect(input?.execution_context_id).toMatch(/^connect-mcps_test_/);
|
|
382
|
+
// The id is the one shape the runner-credential lane recognizes as a
|
|
383
|
+
// connect binding — builder and predicate share one module.
|
|
384
|
+
expect(isConnectExecutionId(input?.execution_context_id ?? "")).toBe(true);
|
|
372
385
|
// oss#535: the decrypt-lane token rides the payload.
|
|
373
386
|
expect(input?.execution_context_token).toBeTruthy();
|
|
374
387
|
});
|
|
@@ -584,6 +597,8 @@ describe("startConnect (async lane)", () => {
|
|
|
584
597
|
);
|
|
585
598
|
expect(result.metadata?.id).toBe(server.metadata!.id);
|
|
586
599
|
expect(harness.ecCreates).toBe(1);
|
|
600
|
+
// The async lane creates the EC as the caller too — one prepareConnect.
|
|
601
|
+
expect(harness.ecCreators).toEqual([testCaller]);
|
|
587
602
|
await vi.waitFor(() => expect(harness.ecDeletes).toEqual(["ectx_1"]));
|
|
588
603
|
});
|
|
589
604
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The synthetic execution id of an MCP connect — the ONE home for its
|
|
3
|
+
* shape, built here by the connect lane and recognized here by the
|
|
4
|
+
* runner-credential lane, so the two can never drift apart.
|
|
5
|
+
*
|
|
6
|
+
* A connect is not an execution: it has no AgentExecution or
|
|
7
|
+
* WorkflowExecution row. What it has is an ephemeral ExecutionContext
|
|
8
|
+
* (connect.ts `createConnectExecutionContext`) whose `spec.execution_id`
|
|
9
|
+
* must name SOMETHING for the decrypt lane's binding check
|
|
10
|
+
* (executioncontext/resolve-values-for-caller.ts: the token's
|
|
11
|
+
* `execution_id` claim must equal the EC's), and this id is that
|
|
12
|
+
* something — an id that names no resource kind, so the contract's
|
|
13
|
+
* `kind_meta` prefix table (pipeline/apiresource-meta.ts) cannot mistake
|
|
14
|
+
* it for a row. The runner-credential lane reads it the other way round
|
|
15
|
+
* (runnerauth/bound-execution.ts `boundExecutionKindOf`): an id this
|
|
16
|
+
* predicate recognizes is a connect binding, resolved through the EC row
|
|
17
|
+
* whose `spec.execution_id` it is. Keep the builder and the predicate
|
|
18
|
+
* together; a second copy of the prefix anywhere would be the drift this
|
|
19
|
+
* module exists to prevent.
|
|
20
|
+
*/
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
|
|
23
|
+
/** The prefix every connect execution id carries — no resource kind's id starts with it. */
|
|
24
|
+
const CONNECT_EXECUTION_ID_PREFIX = "connect-";
|
|
25
|
+
|
|
26
|
+
/** One connect's execution id: the server it discovers plus eight hex of entropy per attempt. */
|
|
27
|
+
export function newConnectExecutionId(mcpServerId: string): string {
|
|
28
|
+
return `${CONNECT_EXECUTION_ID_PREFIX}${mcpServerId}-${randomUUID().slice(0, 8)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Whether `executionId` is a connect's — the runner-credential lane's recognition of the third binding. */
|
|
32
|
+
export function isConnectExecutionId(executionId: string): boolean {
|
|
33
|
+
return executionId.startsWith(CONNECT_EXECUTION_ID_PREFIX);
|
|
34
|
+
}
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
import { create } from "@bufbuild/protobuf";
|
|
18
18
|
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
19
19
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
20
|
-
import { randomUUID } from "node:crypto";
|
|
21
20
|
|
|
22
21
|
import type {
|
|
23
22
|
EnvironmentList,
|
|
@@ -62,6 +61,7 @@ import type {
|
|
|
62
61
|
} from "../../store/interface.js";
|
|
63
62
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
64
63
|
import { resolveOAuthAppRef } from "../oauthapp/refresolution.js";
|
|
64
|
+
import { newConnectExecutionId } from "./connect-execution-id.js";
|
|
65
65
|
import {
|
|
66
66
|
persistConnectFailure,
|
|
67
67
|
persistConnectResult,
|
|
@@ -169,10 +169,21 @@ export interface ConnectEnvironmentReader {
|
|
|
169
169
|
/**
|
|
170
170
|
* The ExecutionContext lifecycle surface for the ephemeral connect EC —
|
|
171
171
|
* Go's downstream executioncontext client (create + delete).
|
|
172
|
+
*
|
|
173
|
+
* `create` takes the connecting caller and the composition creates the
|
|
174
|
+
* row AS THAT PERSON (boot/inprocess.ts, the `asCaller` lane of ruling
|
|
175
|
+
* R5), never under the internal class: the row's creator stamp is what
|
|
176
|
+
* the runner-subject verifier resolves the connect's person from when
|
|
177
|
+
* the runner presents the connect token under the built-in posture
|
|
178
|
+
* (runnerauth/bound-execution.ts, the `mcp-connect` binding). A row
|
|
179
|
+
* stamped `internal` would name nobody, and the credentialed connect
|
|
180
|
+
* would be refused at identity on every enforcing self-host. `delete`
|
|
181
|
+
* stays the server's own act: the row is the lane's, whoever asked.
|
|
172
182
|
*/
|
|
173
183
|
export interface ConnectExecutionContextClient {
|
|
174
184
|
create(
|
|
175
185
|
executionContext: MessageInitShape<typeof ExecutionContextSchema>,
|
|
186
|
+
caller: CallerIdentity,
|
|
176
187
|
): Promise<ExecutionContext>;
|
|
177
188
|
delete(
|
|
178
189
|
input: MessageInitShape<typeof ApiResourceDeleteInputSchema>,
|
|
@@ -272,7 +283,7 @@ export async function connect(
|
|
|
272
283
|
input,
|
|
273
284
|
);
|
|
274
285
|
|
|
275
|
-
const prepared = await prepareConnect(deps, mcpServer, input);
|
|
286
|
+
const prepared = await prepareConnect(deps, mcpServer, input, identity);
|
|
276
287
|
|
|
277
288
|
try {
|
|
278
289
|
let run: ConnectRun;
|
|
@@ -385,11 +396,28 @@ export async function connect(
|
|
|
385
396
|
* read the caller's grant and secrets, which a background task has no
|
|
386
397
|
* request context to do (the same constraint that scopes
|
|
387
398
|
* startBestEffortConnect to env-less servers).
|
|
399
|
+
*
|
|
400
|
+
* A connect's discovery runs on the runner with TWO credentials, and the
|
|
401
|
+
* split is deliberate. The runner's own process credential reads the
|
|
402
|
+
* McpServer's metadata — under the built-in posture that credential is
|
|
403
|
+
* the operator's API key, and the operator is an organization admin
|
|
404
|
+
* (deploy/helm/stigmer: the signed-in operator mints the runner's key),
|
|
405
|
+
* whom the model makes an owner of every McpServer in the organization
|
|
406
|
+
* (authorization/model/mcp_server.ts), so a member's private server is
|
|
407
|
+
* readable. The connect token minted below reads the SECRETS: it is
|
|
408
|
+
* bound to this connect's ExecutionContext, and under the built-in
|
|
409
|
+
* posture the runner-subject verifier resolves its bearer to the person
|
|
410
|
+
* that row was created by — which is why the row is created as `identity`
|
|
411
|
+
* and not as the server. The end state where every RPC of a discovery
|
|
412
|
+
* acts as the person (the cloud's connect-sandbox shape) needs a row an
|
|
413
|
+
* env-less connect would also create; it creates none today, so that is
|
|
414
|
+
* a design act of its own, not a widening to make here.
|
|
388
415
|
*/
|
|
389
416
|
export async function prepareConnect(
|
|
390
417
|
deps: McpServerConnectDeps,
|
|
391
418
|
mcpServer: McpServer,
|
|
392
419
|
input: ConnectInput,
|
|
420
|
+
identity: CallerIdentity,
|
|
393
421
|
): Promise<PreparedConnect> {
|
|
394
422
|
const mcpServerId = mcpServer.metadata?.id ?? "";
|
|
395
423
|
const callerOrg = input.org;
|
|
@@ -402,7 +430,7 @@ export async function prepareConnect(
|
|
|
402
430
|
await refreshOAuthTokenIfNeeded(deps, mcpServer, callerOrg);
|
|
403
431
|
}
|
|
404
432
|
|
|
405
|
-
const executionId =
|
|
433
|
+
const executionId = newConnectExecutionId(mcpServerId);
|
|
406
434
|
|
|
407
435
|
const ecResourceId = await createConnectExecutionContext(
|
|
408
436
|
deps,
|
|
@@ -410,6 +438,7 @@ export async function prepareConnect(
|
|
|
410
438
|
executionId,
|
|
411
439
|
callerOrg,
|
|
412
440
|
input.runtimeEnv,
|
|
441
|
+
identity,
|
|
413
442
|
);
|
|
414
443
|
|
|
415
444
|
const workflowInput: ConnectWorkflowInput = {
|
|
@@ -464,6 +493,10 @@ export async function prepareConnect(
|
|
|
464
493
|
* sources: OAuth-managed variables from the grant's managed environment,
|
|
465
494
|
* the remainder from the user's personal environment. Returns "" when the
|
|
466
495
|
* MCP server has no env declarations and no runtime_env.
|
|
496
|
+
*
|
|
497
|
+
* The row is created as `caller`, the person connecting (see the
|
|
498
|
+
* ConnectExecutionContextClient doc): its creator stamp is the connect
|
|
499
|
+
* token's person under the built-in posture.
|
|
467
500
|
*/
|
|
468
501
|
async function createConnectExecutionContext(
|
|
469
502
|
deps: McpServerConnectDeps,
|
|
@@ -471,6 +504,7 @@ async function createConnectExecutionContext(
|
|
|
471
504
|
executionId: string,
|
|
472
505
|
callerOrg: string,
|
|
473
506
|
runtimeEnv: { [key: string]: ExecutionValue },
|
|
507
|
+
caller: CallerIdentity,
|
|
474
508
|
): Promise<string> {
|
|
475
509
|
let ecData: { [key: string]: ExecutionValue };
|
|
476
510
|
|
|
@@ -540,6 +574,7 @@ async function createConnectExecutionContext(
|
|
|
540
574
|
data: ecData,
|
|
541
575
|
},
|
|
542
576
|
}),
|
|
577
|
+
caller,
|
|
543
578
|
);
|
|
544
579
|
} catch (error) {
|
|
545
580
|
throw internalError(error, "failed to create connect ExecutionContext");
|
|
@@ -26,9 +26,15 @@
|
|
|
26
26
|
* read RPC redacts secrets unless the caller presents an execution-scoped
|
|
27
27
|
* runner token, and the discovery activity has no execution of its own to
|
|
28
28
|
* exchange for one — the capability travels with the work item. It is a
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* short-TTL token bound to this connect flow's ephemeral EC (deleted when
|
|
30
|
+
* the connect settles, on the blocking lane and the async one alike) and
|
|
31
|
+
* useless once either expires. What it unlocks depends on the posture:
|
|
32
|
+
* under trusted-local it is a decrypt-lane discriminator and nothing more;
|
|
33
|
+
* under the built-in authorization posture the same token also admits its
|
|
34
|
+
* bearer AS THE PERSON who asked for the connect, on every RPC, for as
|
|
35
|
+
* long as the EC row exists (runnerauth/runnerauth.ts, the two lanes by
|
|
36
|
+
* posture; runnerauth/bound-execution.ts, the `mcp-connect` binding). It
|
|
37
|
+
* sits in Temporal history in the clear like every server-written input.
|
|
32
38
|
*/
|
|
33
39
|
export interface ConnectWorkflowInput {
|
|
34
40
|
readonly mcp_server_id: string;
|