@stigmer/server 3.15.3-dev.20260916211208 → 3.16.1-dev.20260917224426
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 +12 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +68 -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 +31 -11
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -3
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/plugin/materialize/agent.d.ts +10 -1
- package/dist/domain/plugin/materialize/agent.d.ts.map +1 -1
- package/dist/domain/plugin/materialize/agent.js +35 -1
- package/dist/domain/plugin/materialize/agent.js.map +1 -1
- package/dist/domain/plugin/materialize/plan.d.ts.map +1 -1
- package/dist/domain/plugin/materialize/plan.js +1 -1
- package/dist/domain/plugin/materialize/plan.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/project/reconcile.d.ts.map +1 -1
- package/dist/domain/project/reconcile.js +14 -10
- package/dist/domain/project/reconcile.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 +93 -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/__tests__/platform.test.ts +47 -1
- package/src/domain/platform/controller.ts +33 -14
- package/src/domain/plugin/__tests__/materialize.test.ts +60 -1
- package/src/domain/plugin/__tests__/plugin.test.ts +122 -1
- package/src/domain/plugin/__tests__/sanitize.test.ts +2 -2
- package/src/domain/plugin/materialize/agent.ts +38 -0
- package/src/domain/plugin/materialize/plan.ts +7 -1
- package/src/domain/plugin/overlay/sanitize.ts +4 -2
- package/src/domain/project/__tests__/reconcile.test.ts +51 -5
- package/src/domain/project/reconcile.ts +20 -15
- 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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane admission over the Authorizer seam — open source's rendering of
|
|
3
|
+
* the cloud's decorator (stigmer-cloud `authorizer/lane-admission.ts`,
|
|
4
|
+
* stigmer-cloud#709), composed around the built-in Authorizer under the
|
|
5
|
+
* built-in authorization posture (boot/compose.ts).
|
|
6
|
+
*
|
|
7
|
+
* The OSS AuthorizeRunTarget step asks the Authorizer, for every caller
|
|
8
|
+
* but the internal class, whether the caller may run what a session or
|
|
9
|
+
* execution targets — the run-gate checks (`isRunGateCheck`, the
|
|
10
|
+
* OSS-owned definition of that set: `can_execute` on the blueprints and
|
|
11
|
+
* instances, `can_create_execution_in` on a session). A runner acting
|
|
12
|
+
* for a WORKFLOW execution creates the workflow's child agent executions
|
|
13
|
+
* (the `agent_call` activity), and it does so AS THE HUMAN whose workflow
|
|
14
|
+
* it is (runnerauth/runner-subject-verifier.ts). Checked as that human,
|
|
15
|
+
* a member running an organization-visible workflow that calls another
|
|
16
|
+
* member's private agent would be refused at the child create — but
|
|
17
|
+
* the parent workflow execution already passed its own run gate, and
|
|
18
|
+
* the human's visibility of the agents a shared workflow calls is not
|
|
19
|
+
* the question. The cloud admits its `workflow_sandbox` lane on exactly
|
|
20
|
+
* these checks for exactly this reason; this decorator gives an
|
|
21
|
+
* enforcing self-host the same answer, so a workflow behaves the same in
|
|
22
|
+
* both editions (the parent program's rule: nothing diverges silently).
|
|
23
|
+
*
|
|
24
|
+
* Why a predicate and not a class set: the cloud keys its decorator on
|
|
25
|
+
* `callerClass` because there the class IS the token type. In open
|
|
26
|
+
* source the class is `runner` for every bound execution, and the
|
|
27
|
+
* binding's KIND lives in the token, readable by the one function that
|
|
28
|
+
* interprets runner tokens (`isWorkflowBoundRunner` in
|
|
29
|
+
* runnerauth/built-in-runner-credential-provider.ts). Handing that
|
|
30
|
+
* reading in as `isAdmittedLane` keeps this module free of token
|
|
31
|
+
* knowledge and lets the cloud adopt the same shape later without a
|
|
32
|
+
* second class name. An AGENT-bound runner is NOT admitted: a subagent
|
|
33
|
+
* create is checked as the human, the cloud's `sandbox` posture.
|
|
34
|
+
*
|
|
35
|
+
* Narrow by construction: an admitted lane on any NON-run-gate check,
|
|
36
|
+
* and any other caller on any check, delegate untouched. The cloud's
|
|
37
|
+
* header records one caveat — a human who can view a workflow execution
|
|
38
|
+
* can exchange for its token and replay it against the run gate. Open
|
|
39
|
+
* source has no such caveat: under this posture the exchange is a mint
|
|
40
|
+
* gate that answers only the run's own person (the built-in provider's
|
|
41
|
+
* exchangeScopedToken, runnerauth/built-in-runner-credential-provider.ts),
|
|
42
|
+
* so the only human who can hold a workflow's credential is the one whose
|
|
43
|
+
* workflow it is — and their own run gate already passed.
|
|
44
|
+
*/
|
|
45
|
+
import type { CallerIdentity } from "../extensions/identity.js";
|
|
46
|
+
import type {
|
|
47
|
+
Authorizer,
|
|
48
|
+
AuthzCheck,
|
|
49
|
+
AuthzDecision,
|
|
50
|
+
} from "../extensions/authorizer.js";
|
|
51
|
+
import { isRunGateCheck } from "../pipeline/steps/authorize-run-target.js";
|
|
52
|
+
|
|
53
|
+
const ADMITTED: AuthzDecision = { kind: "allow" };
|
|
54
|
+
|
|
55
|
+
/** Whether `caller` is a lane whose admission to CREATE a run was decided upstream. */
|
|
56
|
+
export type LaneAdmissionPredicate = (caller: CallerIdentity) => boolean;
|
|
57
|
+
|
|
58
|
+
export function newLaneAdmittedAuthorizer(
|
|
59
|
+
inner: Authorizer,
|
|
60
|
+
isAdmittedLane: LaneAdmissionPredicate,
|
|
61
|
+
): Authorizer {
|
|
62
|
+
return {
|
|
63
|
+
authorize(
|
|
64
|
+
caller: CallerIdentity,
|
|
65
|
+
check: AuthzCheck,
|
|
66
|
+
): Promise<AuthzDecision> {
|
|
67
|
+
if (isRunGateCheck(check) && isAdmittedLane(caller)) {
|
|
68
|
+
return Promise.resolve(ADMITTED);
|
|
69
|
+
}
|
|
70
|
+
return inner.authorize(caller, check);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
* client-suppliable schedule-id label is what the cloud explicitly
|
|
23
23
|
* refused.
|
|
24
24
|
*
|
|
25
|
-
* The creator's account is resolved from the row's stamp
|
|
26
|
-
* account
|
|
27
|
-
*
|
|
28
|
-
* same two shapes `Person` aliases
|
|
25
|
+
* The creator's account is resolved from the row's stamp through the
|
|
26
|
+
* identity-account domain's `accountForStamp` — as an account id (rows
|
|
27
|
+
* stamped since 3.15.0) and as the raw issuer subject (rows the 3.14.x
|
|
28
|
+
* verifiers stamped), the same two shapes `Person` aliases and the same
|
|
29
|
+
* read the runner-subject verifier makes of an execution's stamp. A
|
|
30
|
+
* stamp that resolves to no account
|
|
29
31
|
* is the seam's DETERMINISTIC refusal (`ScheduleFireCallerRefusedError`):
|
|
30
32
|
* the empty stamp, the laptop's `"system"`, an email from the
|
|
31
33
|
* trusted-local era, a deleted account — no retry will make that schedule
|
|
@@ -43,7 +45,8 @@ import { ScheduleSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/a
|
|
|
43
45
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
44
46
|
|
|
45
47
|
import { accountAsCaller } from "../domain/identityaccount/actor.js";
|
|
46
|
-
import
|
|
48
|
+
import { accountForStamp } from "../domain/identityaccount/resolve.js";
|
|
49
|
+
import type { AccountsByCaller } from "../domain/identityaccount/resolve.js";
|
|
47
50
|
import type { ScheduleFireCallerMint } from "../extensions/schedule-fire-caller.js";
|
|
48
51
|
import { ScheduleFireCallerRefusedError } from "../extensions/schedule-fire-caller.js";
|
|
49
52
|
import { auditOf } from "../pipeline/steps/defaults.js";
|
|
@@ -52,10 +55,7 @@ import type { Store } from "../store/interface.js";
|
|
|
52
55
|
export interface BuiltInScheduleFireCallerDeps {
|
|
53
56
|
readonly store: Store;
|
|
54
57
|
/** The account port: the stamp is resolved as an account id, then as a direct subject. */
|
|
55
|
-
readonly accounts:
|
|
56
|
-
IdentityAccountStore,
|
|
57
|
-
"findById" | "findDirectByIdpId"
|
|
58
|
-
>;
|
|
58
|
+
readonly accounts: AccountsByCaller;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** The refusal's copy — written for the person who scheduled the run (the schedule's status carries it). */
|
|
@@ -75,11 +75,7 @@ export function newBuiltInScheduleFireCaller(
|
|
|
75
75
|
);
|
|
76
76
|
const stamp =
|
|
77
77
|
auditOf(ScheduleSchema, schedule)?.specAudit?.createdBy?.id ?? "";
|
|
78
|
-
const account =
|
|
79
|
-
stamp === ""
|
|
80
|
-
? undefined
|
|
81
|
-
: ((await deps.accounts.findById(stamp)) ??
|
|
82
|
-
(await deps.accounts.findDirectByIdpId(stamp)));
|
|
78
|
+
const account = await accountForStamp(deps.accounts, stamp);
|
|
83
79
|
if (account === undefined) {
|
|
84
80
|
throw new ScheduleFireCallerRefusedError(
|
|
85
81
|
scheduleHasNoPersonMessage(scheduleId),
|
package/src/boot/compose.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_inf
|
|
|
33
33
|
import { HealthCheckResponse_ServingStatus as ServingStatus } from "@stigmer/protos/grpc/health/v1/health_pb";
|
|
34
34
|
|
|
35
35
|
import { newBuiltInAuthorizer } from "../authorization/authorizer.js";
|
|
36
|
+
import { newLaneAdmittedAuthorizer } from "../authorization/lane-admission.js";
|
|
36
37
|
import { newBuiltInListReadScope } from "../authorization/list-read-scope.js";
|
|
37
38
|
import { newBuiltInOrganizationDirectory } from "../authorization/organization-directory.js";
|
|
38
39
|
import { authorizationPostureOf } from "../authorization/posture.js";
|
|
@@ -107,6 +108,7 @@ import { registerOAuthAppServices } from "../domain/oauthapp/controller.js";
|
|
|
107
108
|
import { registerOrganizationServices } from "../domain/organization/controller.js";
|
|
108
109
|
import { registerMcpServerServices } from "../domain/mcpserver/controller.js";
|
|
109
110
|
import { registerPlatformServices } from "../domain/platform/controller.js";
|
|
111
|
+
import { SERVER_VERSION } from "../domain/platform/version.js";
|
|
110
112
|
import { registerProjectServices } from "../domain/project/controller.js";
|
|
111
113
|
import { registerSessionServices } from "../domain/session/controller.js";
|
|
112
114
|
import { registerPluginServices } from "../domain/plugin/controller.js";
|
|
@@ -150,7 +152,12 @@ import { operatorIdentitySnapshot } from "../pipeline/steps/defaults.js";
|
|
|
150
152
|
import { createErrorBoundaryInterceptor } from "../pipeline/interceptors/error-boundary.js";
|
|
151
153
|
import { createRequestMetricsInterceptor } from "../pipeline/interceptors/request-metrics.js";
|
|
152
154
|
import { newPermissiveSingleTeamAuthorizer } from "../pipeline/steps/authorize.js";
|
|
155
|
+
import {
|
|
156
|
+
isWorkflowBoundRunner,
|
|
157
|
+
newBuiltInRunnerCredentialProvider,
|
|
158
|
+
} from "../runnerauth/built-in-runner-credential-provider.js";
|
|
153
159
|
import { newExecutionScopedRunnerCredentialProvider } from "../runnerauth/runner-credential-provider.js";
|
|
160
|
+
import { newRunnerSubjectIdentityVerifier } from "../runnerauth/runner-subject-verifier.js";
|
|
154
161
|
import { RunnerAuthService } from "../runnerauth/runnerauth.js";
|
|
155
162
|
import { PostgresStore } from "../store/postgres/store.js";
|
|
156
163
|
import { SqliteStore } from "../store/sqlite/store.js";
|
|
@@ -276,6 +283,18 @@ export interface ComposeOptions {
|
|
|
276
283
|
* conformance rosters.
|
|
277
284
|
*/
|
|
278
285
|
extensions?: ReadonlyArray<ServerExtension>;
|
|
286
|
+
/**
|
|
287
|
+
* The release this server reports through getServerInfo. Defaults to
|
|
288
|
+
* SERVER_VERSION: the esbuild define the all-in-one bundle stamps, or
|
|
289
|
+
* "dev" when unbundled. A composition that consumes @stigmer/server as a
|
|
290
|
+
* library runs no bundle and would otherwise report "dev" whatever
|
|
291
|
+
* release it installed, so it states the version of the package it
|
|
292
|
+
* composes (the installed package's own manifest is the honest source).
|
|
293
|
+
* A fact about the library the consumer installed, not about any
|
|
294
|
+
* extension unit — which is why it lives here and not on
|
|
295
|
+
* ServerExtension beside `edition`.
|
|
296
|
+
*/
|
|
297
|
+
version?: string;
|
|
279
298
|
/** Test seam forwarded to the model-registry upstream fetch. */
|
|
280
299
|
fetchImpl?: typeof fetch;
|
|
281
300
|
/** Test seam: bind an ephemeral port instead of config.grpcPort. */
|
|
@@ -431,12 +450,44 @@ export async function composeServer(
|
|
|
431
450
|
operatorEmail: operatorIdentity.email,
|
|
432
451
|
})
|
|
433
452
|
: undefined;
|
|
453
|
+
// Stage: runner credentials (§6c, O5). The concrete service is
|
|
454
|
+
// constructed and exposed UNCONDITIONALLY — its boot-fatal key posture
|
|
455
|
+
// is the ratified cross-domain invariant, and the boot/EC-decrypt tests
|
|
456
|
+
// mint against the server's own key through it. The provider over it is
|
|
457
|
+
// the edition's whole credential story in one object: a composed
|
|
458
|
+
// driver; otherwise, under the built-in authorization posture, open
|
|
459
|
+
// source's built-in provider (runnerauth/built-in-runner-credential-
|
|
460
|
+
// provider.ts — the execution-scoped default plus the lineage-vouching,
|
|
461
|
+
// memory-capture and exchange-gating capabilities an enforcing
|
|
462
|
+
// self-host needs, and the one reading of a runner's binding the
|
|
463
|
+
// Authorizer below admits a workflow-bound runner on); otherwise the
|
|
464
|
+
// execution-scoped default, byte-identical to the wiring this seam
|
|
465
|
+
// replaced. Both open-source providers also mint the RUN credential the
|
|
466
|
+
// two execution engines put on every dispatch (the engines below take
|
|
467
|
+
// this object; runnerauth/dispatch-credential.ts) — a composed driver
|
|
468
|
+
// that leaves that capability undefined dispatches without one, its
|
|
469
|
+
// runners being credentialed another way. Bound HERE, before the
|
|
470
|
+
// Authorizer, because the lane admission reads the binding.
|
|
471
|
+
const runnerAuthService = RunnerAuthService.fromEnv();
|
|
472
|
+
const runnerCredentials =
|
|
473
|
+
extensions.drivers.runnerCredentialProvider ??
|
|
474
|
+
(authorizationPosture === "built-in"
|
|
475
|
+
? newBuiltInRunnerCredentialProvider({
|
|
476
|
+
service: runnerAuthService,
|
|
477
|
+
store,
|
|
478
|
+
accounts: identityAccounts,
|
|
479
|
+
})
|
|
480
|
+
: newExecutionScopedRunnerCredentialProvider(runnerAuthService));
|
|
434
481
|
// The ONE composed Authorizer (DD-007 §3), bound here — after the store,
|
|
435
482
|
// the account port and the IamPolicy port it reads — and handed to every
|
|
436
483
|
// controller as an explicit dependency; the Authorize step at position 1
|
|
437
484
|
// of every chain calls it (O2). By posture: an extension's registration;
|
|
438
485
|
// open source's built-in Authorizer (authorization/authorizer.ts — the
|
|
439
|
-
// cloud's model evaluated over the tuples the row would have had)
|
|
486
|
+
// cloud's model evaluated over the tuples the row would have had),
|
|
487
|
+
// decorated with the lane admission (authorization/lane-admission.ts) so
|
|
488
|
+
// a runner acting for a WORKFLOW execution passes the run gate on the
|
|
489
|
+
// child executions its `agent_call` creates — the parent workflow
|
|
490
|
+
// already passed its own, the cloud's `workflow_sandbox` posture; or
|
|
440
491
|
// the permissive single-team default. The organization directory and the
|
|
441
492
|
// schedule fire caller are bound beside it under the same posture, so
|
|
442
493
|
// `findMyOrganizations` lists what a person may view and a scheduled run
|
|
@@ -445,13 +496,16 @@ export async function composeServer(
|
|
|
445
496
|
const authorizer: Authorizer =
|
|
446
497
|
extensions.authorizer ??
|
|
447
498
|
(authorizationPosture === "built-in"
|
|
448
|
-
?
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
499
|
+
? newLaneAdmittedAuthorizer(
|
|
500
|
+
newBuiltInAuthorizer({
|
|
501
|
+
store,
|
|
502
|
+
policies: iamPolicies,
|
|
503
|
+
accounts: identityAccounts,
|
|
504
|
+
edition: extensions.edition,
|
|
505
|
+
logger,
|
|
506
|
+
}),
|
|
507
|
+
(caller) => isWorkflowBoundRunner(runnerAuthService, caller),
|
|
508
|
+
)
|
|
455
509
|
: newPermissiveSingleTeamAuthorizer());
|
|
456
510
|
const organizationDirectory: OrganizationDirectory | undefined =
|
|
457
511
|
extensions.drivers.organizationDirectory ??
|
|
@@ -626,16 +680,6 @@ export async function composeServer(
|
|
|
626
680
|
writeVersionValue === "" ? DEFAULT_WRITE_VERSION : writeVersionValue,
|
|
627
681
|
registeredCodecs: [...secretCodecs.keys()].sort(),
|
|
628
682
|
});
|
|
629
|
-
const runnerAuthService = RunnerAuthService.fromEnv();
|
|
630
|
-
// The runner-credential seam (§6c, O5): the composed provider, or the
|
|
631
|
-
// OSS execution-scoped default over the service above. The concrete
|
|
632
|
-
// service is constructed and exposed UNCONDITIONALLY either way — its
|
|
633
|
-
// boot-fatal key posture is the ratified cross-domain invariant, and the
|
|
634
|
-
// boot/EC-decrypt tests mint against the server's own key through it.
|
|
635
|
-
const runnerCredentials =
|
|
636
|
-
extensions.drivers.runnerCredentialProvider ??
|
|
637
|
-
newExecutionScopedRunnerCredentialProvider(runnerAuthService);
|
|
638
|
-
|
|
639
683
|
// Stage: temporal (#18). Construction is sync and connection-free —
|
|
640
684
|
// initialConnect/startWorkers/startHealthMonitor run in start(), all
|
|
641
685
|
// NON-fatal (Go server.go: the server boots and serves with the engine
|
|
@@ -837,6 +881,7 @@ export async function composeServer(
|
|
|
837
881
|
manager: temporalManager,
|
|
838
882
|
config: temporalConfig,
|
|
839
883
|
store,
|
|
884
|
+
runnerCredentials,
|
|
840
885
|
logger,
|
|
841
886
|
});
|
|
842
887
|
// The workflow-execution twin: the same provider-is-the-injection
|
|
@@ -844,6 +889,7 @@ export async function composeServer(
|
|
|
844
889
|
const workflowExecutionEngineState = newWorkflowExecutionEngineStateProvider({
|
|
845
890
|
manager: temporalManager,
|
|
846
891
|
config: workflowExecutionTemporalConfig,
|
|
892
|
+
runnerCredentials,
|
|
847
893
|
logger,
|
|
848
894
|
});
|
|
849
895
|
// The artifact blob store (Go server.go 349: shared by agentexecution
|
|
@@ -1355,8 +1401,8 @@ export async function composeServer(
|
|
|
1355
1401
|
requireInProcess().executionEnvironmentReader.getSecretValue(input),
|
|
1356
1402
|
},
|
|
1357
1403
|
executionContext: {
|
|
1358
|
-
create: (ec) =>
|
|
1359
|
-
requireInProcess().connectExecutionContextClient.create(ec),
|
|
1404
|
+
create: (ec, caller) =>
|
|
1405
|
+
requireInProcess().connectExecutionContextClient.create(ec, caller),
|
|
1360
1406
|
delete: (input) =>
|
|
1361
1407
|
requireInProcess().connectExecutionContextClient.delete(input),
|
|
1362
1408
|
},
|
|
@@ -1439,6 +1485,7 @@ export async function composeServer(
|
|
|
1439
1485
|
temporalNamespace: config.temporalNamespace,
|
|
1440
1486
|
runnerAuthService: runnerCredentials,
|
|
1441
1487
|
edition: extensions.edition,
|
|
1488
|
+
version: options.version ?? SERVER_VERSION,
|
|
1442
1489
|
logger,
|
|
1443
1490
|
});
|
|
1444
1491
|
// Extension services register after the whole OSS set, inside the ONE
|
|
@@ -1474,12 +1521,36 @@ export async function composeServer(
|
|
|
1474
1521
|
// port the domain writes through (`identityAccounts`, the
|
|
1475
1522
|
// identity-accounts stage — a composed driver included), so a row
|
|
1476
1523
|
// provisioning creates resolves on the very next request.
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1524
|
+
// - the runner-subject verifier rides the AUTHORIZATION posture
|
|
1525
|
+
// (`built-in`, beside the Authorizer and the fire caller above): it
|
|
1526
|
+
// admits the bearer of the server's own execution-scoped runner
|
|
1527
|
+
// token as the human whose run it is, which only means something
|
|
1528
|
+
// when the server enforces who may report on a run. Its place in
|
|
1529
|
+
// the chain is the contract: BETWEEN `apikey` and `oidc`, because
|
|
1530
|
+
// the OIDC verifier claims any JWT-shaped token and throws on one
|
|
1531
|
+
// it cannot verify — composed after it, ours would never see its
|
|
1532
|
+
// own token, and every runner call would fail as the OIDC verifier's
|
|
1533
|
+
// fault (stigmer#1137). Until 2026-09-16 this comment read "the
|
|
1534
|
+
// runner's credential in either posture is an operator-minted API
|
|
1535
|
+
// token via STIGMER_TOKEN — no runner-specific verifier"; that
|
|
1536
|
+
// described a server whose Authorizer was permissive. The operator's
|
|
1537
|
+
// key is still the runner's PROCESS credential (bootstrap, registry);
|
|
1538
|
+
// the run credential is how it acts for each run. A unit with its
|
|
1539
|
+
// own Authorizer composes none of this — its credential story is its
|
|
1540
|
+
// own (the cloud's sandbox lanes).
|
|
1479
1541
|
const identityVerifiers = [
|
|
1480
1542
|
...(requireAuthentication
|
|
1481
1543
|
? [newApiKeyIdentityVerifier({ store, accounts: identityAccounts })]
|
|
1482
1544
|
: []),
|
|
1545
|
+
...(authorizationPosture === "built-in"
|
|
1546
|
+
? [
|
|
1547
|
+
newRunnerSubjectIdentityVerifier({
|
|
1548
|
+
store,
|
|
1549
|
+
accounts: identityAccounts,
|
|
1550
|
+
credentials: runnerCredentials,
|
|
1551
|
+
}),
|
|
1552
|
+
]
|
|
1553
|
+
: []),
|
|
1483
1554
|
...(authEnabled
|
|
1484
1555
|
? [
|
|
1485
1556
|
newOidcIdentityVerifier({
|
package/src/boot/inprocess.ts
CHANGED
|
@@ -332,8 +332,14 @@ export function createInProcessClients(
|
|
|
332
332
|
executionContextCreator: {
|
|
333
333
|
create: (ec) => executionContextCommand.create(ec),
|
|
334
334
|
},
|
|
335
|
+
// The connect lane's ephemeral EC is created AS THE CONNECTING PERSON
|
|
336
|
+
// (ruling R5's asCaller lane): its creator stamp is what the
|
|
337
|
+
// runner-subject verifier resolves the connect token's bearer to under
|
|
338
|
+
// the built-in posture (runnerauth/bound-execution.ts). The delete
|
|
339
|
+
// stays the server's own act — the row is the lane's, whoever asked.
|
|
335
340
|
connectExecutionContextClient: {
|
|
336
|
-
create: (ec) =>
|
|
341
|
+
create: (ec, caller) =>
|
|
342
|
+
executionContextCommand.create(ec, asCaller(caller)),
|
|
337
343
|
delete: (input) => executionContextCommand.delete(input),
|
|
338
344
|
},
|
|
339
345
|
// The workflowexecution edges: the default-instance self-heal creates
|
|
@@ -36,6 +36,8 @@ import type { Client, Transport } from "@connectrpc/connect";
|
|
|
36
36
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
37
37
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
38
38
|
|
|
39
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
40
|
+
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
39
41
|
import { ExecutionContextSchema } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/api_pb";
|
|
40
42
|
import type { ExecutionContext } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/api_pb";
|
|
41
43
|
import { ExecutionContextCommandController } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/command_pb";
|
|
@@ -53,6 +55,7 @@ import {
|
|
|
53
55
|
} from "../../../encryption/encryption.js";
|
|
54
56
|
import { SqliteStore } from "../../../store/sqlite/store.js";
|
|
55
57
|
import { REDACTED_MARKER } from "../../environment/constants.js";
|
|
58
|
+
import { newConnectExecutionId } from "../../mcpserver/connect-execution-id.js";
|
|
56
59
|
import {
|
|
57
60
|
DELETE_EXECUTION_CONTEXT_ACTIVITY_NAME,
|
|
58
61
|
deleteExecutionContextForExecution,
|
|
@@ -419,6 +422,107 @@ describe("executioncontext domain (encryption + runner auth enabled)", () => {
|
|
|
419
422
|
});
|
|
420
423
|
});
|
|
421
424
|
|
|
425
|
+
describe("the run credential on the decrypt lane — validity is the row's", () => {
|
|
426
|
+
// A no-`exp` token: the row is the only thing that can end it, so the
|
|
427
|
+
// lane reads the bound execution's liveness for exactly this shape and
|
|
428
|
+
// keeps the clocked arms above untouched (their ECs name no row).
|
|
429
|
+
const RUN_ID = "aex_run_credential";
|
|
430
|
+
|
|
431
|
+
async function seedRun(
|
|
432
|
+
phase: ExecutionPhase,
|
|
433
|
+
completedAt = "",
|
|
434
|
+
): Promise<void> {
|
|
435
|
+
await ts.server.store.saveResource(
|
|
436
|
+
ApiResourceKind.agent_execution,
|
|
437
|
+
RUN_ID,
|
|
438
|
+
AgentExecutionSchema,
|
|
439
|
+
create(AgentExecutionSchema, {
|
|
440
|
+
metadata: { id: RUN_ID, name: RUN_ID, org: "test-org" },
|
|
441
|
+
status: { phase, completedAt },
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
beforeAll(async () => {
|
|
447
|
+
await ts.command.create(ecInput({ executionId: RUN_ID }));
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
async function readRun(): Promise<ExecutionContext> {
|
|
451
|
+
const credential = ts.server.runnerAuthService.mintRunCredential(RUN_ID);
|
|
452
|
+
return await ts.query.getByExecutionId(
|
|
453
|
+
{ executionId: RUN_ID },
|
|
454
|
+
{ headers: bearerHeaders(credential) },
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
it("decrypts while the run is live", async () => {
|
|
459
|
+
await seedRun(ExecutionPhase.EXECUTION_IN_PROGRESS);
|
|
460
|
+
expect((await readRun()).spec!.data["API_TOKEN"]!.value).toBe(
|
|
461
|
+
"super-secret-token",
|
|
462
|
+
);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("still decrypts just after the run ended — the grace covers the writes that trail the terminal stamp", async () => {
|
|
466
|
+
await seedRun(
|
|
467
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
468
|
+
new Date().toISOString(),
|
|
469
|
+
);
|
|
470
|
+
expect((await readRun()).spec!.data["API_TOKEN"]!.value).toBe(
|
|
471
|
+
"super-secret-token",
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("redacts once the run is over past the grace — a token in Temporal history cannot decrypt a finished run's secrets", async () => {
|
|
476
|
+
await seedRun(
|
|
477
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
478
|
+
new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
|
479
|
+
);
|
|
480
|
+
expect((await readRun()).spec!.data["API_TOKEN"]!.value).toBe(
|
|
481
|
+
REDACTED_MARKER,
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("redacts for a terminal run that does not say when it ended — no grace without a timestamp", async () => {
|
|
486
|
+
await seedRun(ExecutionPhase.EXECUTION_FAILED);
|
|
487
|
+
expect((await readRun()).spec!.data["API_TOKEN"]!.value).toBe(
|
|
488
|
+
REDACTED_MARKER,
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("redacts when the credential names a run this server does not have", async () => {
|
|
493
|
+
const orphan = "aex_no_such_run";
|
|
494
|
+
await ts.command.create(ecInput({ executionId: orphan }));
|
|
495
|
+
const credential = ts.server.runnerAuthService.mintRunCredential(orphan);
|
|
496
|
+
const ec = await ts.query.getByExecutionId(
|
|
497
|
+
{ executionId: orphan },
|
|
498
|
+
{ headers: bearerHeaders(credential) },
|
|
499
|
+
);
|
|
500
|
+
expect(ec.spec!.data["API_TOKEN"]!.value).toBe(REDACTED_MARKER);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("redacts for a clockless token bound to a connect's EC — a connect is not a run, and no mint produces that shape (the verifier refuses it through the same predicate)", async () => {
|
|
504
|
+
const connectId = newConnectExecutionId("mcps_conn");
|
|
505
|
+
await ts.command.create(ecInput({ executionId: connectId }));
|
|
506
|
+
const clockless =
|
|
507
|
+
ts.server.runnerAuthService.mintRunCredential(connectId);
|
|
508
|
+
const redacted = await ts.query.getByExecutionId(
|
|
509
|
+
{ executionId: connectId },
|
|
510
|
+
{ headers: bearerHeaders(clockless) },
|
|
511
|
+
);
|
|
512
|
+
expect(redacted.spec!.data["API_TOKEN"]!.value).toBe(REDACTED_MARKER);
|
|
513
|
+
// The connect lane's own CLOCKED token keeps decrypting the same EC:
|
|
514
|
+
// the clocked arms above are untouched by the run-credential rule.
|
|
515
|
+
const clocked = ts.server.runnerAuthService.mint(connectId, 300).token;
|
|
516
|
+
const decrypted = await ts.query.getByExecutionId(
|
|
517
|
+
{ executionId: connectId },
|
|
518
|
+
{ headers: bearerHeaders(clocked) },
|
|
519
|
+
);
|
|
520
|
+
expect(decrypted.spec!.data["API_TOKEN"]!.value).toBe(
|
|
521
|
+
"super-secret-token",
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
422
526
|
describe("decrypt error doctrine", () => {
|
|
423
527
|
it("tampered stored ciphertext drops the ONE key while the read succeeds; intact keys decrypt", async () => {
|
|
424
528
|
const created = await ts.command.create(
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
EncryptionScope,
|
|
20
20
|
SecretService,
|
|
21
21
|
} from "../../../encryption/encryption.js";
|
|
22
|
+
import type { BoundExecutionStore } from "../../../runnerauth/bound-execution.js";
|
|
22
23
|
import { InvalidTokenError } from "../../../runnerauth/runnerauth.js";
|
|
23
24
|
import type { RunnerCredentialProvider } from "../../../runnerauth/runner-credential-provider.js";
|
|
24
25
|
import { resolveValuesForCaller } from "../resolve-values-for-caller.js";
|
|
@@ -31,6 +32,14 @@ const silentLogger = createLogger({
|
|
|
31
32
|
|
|
32
33
|
const secretService = SecretService.create(Buffer.alloc(32, 7));
|
|
33
34
|
|
|
35
|
+
/** The capability arm never reads the store — a store that proves it. */
|
|
36
|
+
const store: BoundExecutionStore = {
|
|
37
|
+
getResource: () =>
|
|
38
|
+
Promise.reject(new Error("the capability arm must not read the store")),
|
|
39
|
+
findByField: () =>
|
|
40
|
+
Promise.reject(new Error("the capability arm must not read the store")),
|
|
41
|
+
};
|
|
42
|
+
|
|
34
43
|
/** A minimal HandlerContext: the resolve path reads only the auth header. */
|
|
35
44
|
function ctxWithBearer(token: string): HandlerContext {
|
|
36
45
|
const headers = new Headers();
|
|
@@ -84,7 +93,12 @@ describe("resolveValuesForCaller (capability delegation — C4)", () => {
|
|
|
84
93
|
const provider = providerWith(async () => true);
|
|
85
94
|
const ec = await executionContext();
|
|
86
95
|
await resolveValuesForCaller(
|
|
87
|
-
{
|
|
96
|
+
{
|
|
97
|
+
logger: silentLogger,
|
|
98
|
+
secretService,
|
|
99
|
+
runnerAuthService: provider,
|
|
100
|
+
store,
|
|
101
|
+
},
|
|
88
102
|
ctxWithBearer("sandbox-token"),
|
|
89
103
|
ec,
|
|
90
104
|
);
|
|
@@ -96,7 +110,12 @@ describe("resolveValuesForCaller (capability delegation — C4)", () => {
|
|
|
96
110
|
const provider = providerWith(async () => false);
|
|
97
111
|
const ec = await executionContext();
|
|
98
112
|
await resolveValuesForCaller(
|
|
99
|
-
{
|
|
113
|
+
{
|
|
114
|
+
logger: silentLogger,
|
|
115
|
+
secretService,
|
|
116
|
+
runnerAuthService: provider,
|
|
117
|
+
store,
|
|
118
|
+
},
|
|
100
119
|
ctxWithBearer("someone-elses-token"),
|
|
101
120
|
ec,
|
|
102
121
|
);
|
|
@@ -107,7 +126,12 @@ describe("resolveValuesForCaller (capability delegation — C4)", () => {
|
|
|
107
126
|
const provider = providerWith(async () => true);
|
|
108
127
|
const ec = await executionContext();
|
|
109
128
|
await resolveValuesForCaller(
|
|
110
|
-
{
|
|
129
|
+
{
|
|
130
|
+
logger: silentLogger,
|
|
131
|
+
secretService,
|
|
132
|
+
runnerAuthService: provider,
|
|
133
|
+
store,
|
|
134
|
+
},
|
|
111
135
|
ctxWithBearer(""),
|
|
112
136
|
ec,
|
|
113
137
|
);
|
|
@@ -121,7 +145,12 @@ describe("resolveValuesForCaller (capability delegation — C4)", () => {
|
|
|
121
145
|
});
|
|
122
146
|
const ec = await executionContext();
|
|
123
147
|
await resolveValuesForCaller(
|
|
124
|
-
{
|
|
148
|
+
{
|
|
149
|
+
logger: silentLogger,
|
|
150
|
+
secretService,
|
|
151
|
+
runnerAuthService: provider,
|
|
152
|
+
store,
|
|
153
|
+
},
|
|
125
154
|
ctxWithBearer("sandbox-token"),
|
|
126
155
|
ec,
|
|
127
156
|
);
|