@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.
Files changed (170) hide show
  1. package/dist/authorization/lane-admission.d.ts +50 -0
  2. package/dist/authorization/lane-admission.d.ts.map +1 -0
  3. package/dist/authorization/lane-admission.js +13 -0
  4. package/dist/authorization/lane-admission.js.map +1 -0
  5. package/dist/authorization/schedule-fire-caller.d.ts +2 -2
  6. package/dist/authorization/schedule-fire-caller.d.ts.map +1 -1
  7. package/dist/authorization/schedule-fire-caller.js +8 -8
  8. package/dist/authorization/schedule-fire-caller.js.map +1 -1
  9. package/dist/boot/compose.d.ts +12 -0
  10. package/dist/boot/compose.d.ts.map +1 -1
  11. package/dist/boot/compose.js +68 -14
  12. package/dist/boot/compose.js.map +1 -1
  13. package/dist/boot/inprocess.d.ts.map +1 -1
  14. package/dist/boot/inprocess.js +6 -1
  15. package/dist/boot/inprocess.js.map +1 -1
  16. package/dist/domain/executioncontext/resolve-values-for-caller.d.ts +33 -14
  17. package/dist/domain/executioncontext/resolve-values-for-caller.d.ts.map +1 -1
  18. package/dist/domain/executioncontext/resolve-values-for-caller.js +32 -8
  19. package/dist/domain/executioncontext/resolve-values-for-caller.js.map +1 -1
  20. package/dist/domain/identityaccount/actor.d.ts +33 -18
  21. package/dist/domain/identityaccount/actor.d.ts.map +1 -1
  22. package/dist/domain/identityaccount/actor.js +20 -3
  23. package/dist/domain/identityaccount/actor.js.map +1 -1
  24. package/dist/domain/identityaccount/resolve.d.ts +19 -0
  25. package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
  26. package/dist/domain/identityaccount/resolve.js +12 -0
  27. package/dist/domain/identityaccount/resolve.js.map +1 -1
  28. package/dist/domain/mcpserver/connect-execution-id.d.ts +5 -0
  29. package/dist/domain/mcpserver/connect-execution-id.d.ts.map +1 -0
  30. package/dist/domain/mcpserver/connect-execution-id.js +32 -0
  31. package/dist/domain/mcpserver/connect-execution-id.js.map +1 -0
  32. package/dist/domain/mcpserver/connect.d.ts +28 -2
  33. package/dist/domain/mcpserver/connect.d.ts.map +1 -1
  34. package/dist/domain/mcpserver/connect.js +27 -7
  35. package/dist/domain/mcpserver/connect.js.map +1 -1
  36. package/dist/domain/mcpserver/engine.d.ts +9 -3
  37. package/dist/domain/mcpserver/engine.d.ts.map +1 -1
  38. package/dist/domain/mcpserver/engine.js.map +1 -1
  39. package/dist/domain/mcpserver/start-connect.js +1 -1
  40. package/dist/domain/mcpserver/start-connect.js.map +1 -1
  41. package/dist/domain/platform/controller.d.ts +31 -11
  42. package/dist/domain/platform/controller.d.ts.map +1 -1
  43. package/dist/domain/platform/controller.js +2 -3
  44. package/dist/domain/platform/controller.js.map +1 -1
  45. package/dist/domain/plugin/materialize/agent.d.ts +10 -1
  46. package/dist/domain/plugin/materialize/agent.d.ts.map +1 -1
  47. package/dist/domain/plugin/materialize/agent.js +35 -1
  48. package/dist/domain/plugin/materialize/agent.js.map +1 -1
  49. package/dist/domain/plugin/materialize/plan.d.ts.map +1 -1
  50. package/dist/domain/plugin/materialize/plan.js +1 -1
  51. package/dist/domain/plugin/materialize/plan.js.map +1 -1
  52. package/dist/domain/plugin/overlay/sanitize.d.ts.map +1 -1
  53. package/dist/domain/plugin/overlay/sanitize.js +4 -2
  54. package/dist/domain/plugin/overlay/sanitize.js.map +1 -1
  55. package/dist/domain/project/reconcile.d.ts.map +1 -1
  56. package/dist/domain/project/reconcile.js +14 -10
  57. package/dist/domain/project/reconcile.js.map +1 -1
  58. package/dist/domain/workflowexecution/phases.d.ts +21 -0
  59. package/dist/domain/workflowexecution/phases.d.ts.map +1 -0
  60. package/dist/domain/workflowexecution/phases.js +31 -0
  61. package/dist/domain/workflowexecution/phases.js.map +1 -0
  62. package/dist/identity/oidc-verifier.js +8 -1
  63. package/dist/identity/oidc-verifier.js.map +1 -1
  64. package/dist/pipeline/apiresource-meta.d.ts +15 -0
  65. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  66. package/dist/pipeline/apiresource-meta.js +34 -0
  67. package/dist/pipeline/apiresource-meta.js.map +1 -1
  68. package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
  69. package/dist/pipeline/steps/guard-memory-capture.js +5 -3
  70. package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
  71. package/dist/runnerauth/bound-execution.d.ts +27 -0
  72. package/dist/runnerauth/bound-execution.d.ts.map +1 -0
  73. package/dist/runnerauth/bound-execution.js +137 -0
  74. package/dist/runnerauth/bound-execution.js.map +1 -0
  75. package/dist/runnerauth/built-in-runner-credential-provider.d.ts +28 -0
  76. package/dist/runnerauth/built-in-runner-credential-provider.d.ts.map +1 -0
  77. package/dist/runnerauth/built-in-runner-credential-provider.js +217 -0
  78. package/dist/runnerauth/built-in-runner-credential-provider.js.map +1 -0
  79. package/dist/runnerauth/constants.d.ts +63 -0
  80. package/dist/runnerauth/constants.d.ts.map +1 -0
  81. package/dist/runnerauth/constants.js +63 -0
  82. package/dist/runnerauth/constants.js.map +1 -0
  83. package/dist/runnerauth/dispatch-credential.d.ts +38 -0
  84. package/dist/runnerauth/dispatch-credential.d.ts.map +1 -0
  85. package/dist/runnerauth/dispatch-credential.js +17 -0
  86. package/dist/runnerauth/dispatch-credential.js.map +1 -0
  87. package/dist/runnerauth/runner-credential-provider.d.ts +49 -11
  88. package/dist/runnerauth/runner-credential-provider.d.ts.map +1 -1
  89. package/dist/runnerauth/runner-credential-provider.js +16 -4
  90. package/dist/runnerauth/runner-credential-provider.js.map +1 -1
  91. package/dist/runnerauth/runner-subject-verifier.d.ts +14 -0
  92. package/dist/runnerauth/runner-subject-verifier.d.ts.map +1 -0
  93. package/dist/runnerauth/runner-subject-verifier.js +115 -0
  94. package/dist/runnerauth/runner-subject-verifier.js.map +1 -0
  95. package/dist/runnerauth/runnerauth.d.ts +33 -4
  96. package/dist/runnerauth/runnerauth.d.ts.map +1 -1
  97. package/dist/runnerauth/runnerauth.js +131 -35
  98. package/dist/runnerauth/runnerauth.js.map +1 -1
  99. package/dist/temporal/agentexecution/engine-client.d.ts +13 -0
  100. package/dist/temporal/agentexecution/engine-client.d.ts.map +1 -1
  101. package/dist/temporal/agentexecution/engine-client.js +10 -2
  102. package/dist/temporal/agentexecution/engine-client.js.map +1 -1
  103. package/dist/temporal/agentexecution/workflow-input.d.ts +13 -0
  104. package/dist/temporal/agentexecution/workflow-input.d.ts.map +1 -1
  105. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.d.ts.map +1 -1
  106. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js +35 -31
  107. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js.map +1 -1
  108. package/dist/temporal/workflowexecution/engine-client.d.ts +24 -0
  109. package/dist/temporal/workflowexecution/engine-client.d.ts.map +1 -1
  110. package/dist/temporal/workflowexecution/engine-client.js +26 -19
  111. package/dist/temporal/workflowexecution/engine-client.js.map +1 -1
  112. package/dist/temporal/workflowexecution/workflow-input.d.ts +14 -0
  113. package/dist/temporal/workflowexecution/workflow-input.d.ts.map +1 -1
  114. package/package.json +5 -5
  115. package/src/authorization/__tests__/lane-admission.test.ts +115 -0
  116. package/src/authorization/lane-admission.ts +73 -0
  117. package/src/authorization/schedule-fire-caller.ts +10 -14
  118. package/src/boot/compose.ts +93 -22
  119. package/src/boot/inprocess.ts +7 -1
  120. package/src/domain/executioncontext/__tests__/executioncontext.test.ts +104 -0
  121. package/src/domain/executioncontext/__tests__/resolve-values-capability.test.ts +33 -4
  122. package/src/domain/executioncontext/resolve-values-for-caller.ts +79 -22
  123. package/src/domain/identityaccount/__tests__/account-for-stamp.test.ts +111 -0
  124. package/src/domain/identityaccount/actor.ts +61 -21
  125. package/src/domain/identityaccount/resolve.ts +31 -0
  126. package/src/domain/mcpserver/__tests__/connect.test.ts +17 -2
  127. package/src/domain/mcpserver/connect-execution-id.ts +34 -0
  128. package/src/domain/mcpserver/connect.ts +38 -3
  129. package/src/domain/mcpserver/engine.ts +9 -3
  130. package/src/domain/mcpserver/start-connect.ts +1 -1
  131. package/src/domain/platform/__tests__/platform.test.ts +47 -1
  132. package/src/domain/platform/controller.ts +33 -14
  133. package/src/domain/plugin/__tests__/materialize.test.ts +60 -1
  134. package/src/domain/plugin/__tests__/plugin.test.ts +122 -1
  135. package/src/domain/plugin/__tests__/sanitize.test.ts +2 -2
  136. package/src/domain/plugin/materialize/agent.ts +38 -0
  137. package/src/domain/plugin/materialize/plan.ts +7 -1
  138. package/src/domain/plugin/overlay/sanitize.ts +4 -2
  139. package/src/domain/project/__tests__/reconcile.test.ts +51 -5
  140. package/src/domain/project/reconcile.ts +20 -15
  141. package/src/domain/workflowexecution/__tests__/phases.test.ts +58 -0
  142. package/src/domain/workflowexecution/phases.ts +33 -0
  143. package/src/extensions/__tests__/extension-composition.test.ts +7 -1
  144. package/src/identity/__tests__/oidc-verifier.test.ts +22 -0
  145. package/src/identity/oidc-verifier.ts +8 -1
  146. package/src/pipeline/__tests__/kind-by-id-prefix.test.ts +75 -0
  147. package/src/pipeline/apiresource-meta.ts +41 -0
  148. package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +55 -35
  149. package/src/pipeline/steps/guard-memory-capture.ts +5 -3
  150. package/src/runnerauth/__tests__/bound-execution.test.ts +334 -0
  151. package/src/runnerauth/__tests__/built-in-runner-credential-provider.test.ts +516 -0
  152. package/src/runnerauth/__tests__/dispatch-credential.test.ts +94 -0
  153. package/src/runnerauth/__tests__/runner-credential-provider.test.ts +27 -0
  154. package/src/runnerauth/__tests__/runner-subject-composed.test.ts +497 -0
  155. package/src/runnerauth/__tests__/runner-subject-verifier.test.ts +541 -0
  156. package/src/runnerauth/bound-execution.ts +270 -0
  157. package/src/runnerauth/built-in-runner-credential-provider.ts +300 -0
  158. package/src/runnerauth/constants.ts +74 -0
  159. package/src/runnerauth/dispatch-credential.ts +63 -0
  160. package/src/runnerauth/runner-credential-provider.ts +60 -11
  161. package/src/runnerauth/runner-subject-verifier.ts +152 -0
  162. package/src/runnerauth/runnerauth.ts +148 -38
  163. package/src/temporal/agentexecution/__tests__/engine-client.test.ts +107 -38
  164. package/src/temporal/agentexecution/__tests__/invoke-workflow.test.ts +105 -2
  165. package/src/temporal/agentexecution/engine-client.ts +27 -2
  166. package/src/temporal/agentexecution/workflow-input.ts +13 -0
  167. package/src/temporal/agentexecution/workflows/invoke-agent-execution.ts +90 -44
  168. package/src/temporal/workflowexecution/__tests__/engine-client.test.ts +139 -33
  169. package/src/temporal/workflowexecution/engine-client.ts +48 -21
  170. 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 two ways — as an
26
- * account id (rows stamped since 3.15.0) and as the raw issuer subject
27
- * (rows the 3.14.x verifiers stamped) — through the account port, the
28
- * same two shapes `Person` aliases. A stamp that resolves to no account
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 type { IdentityAccountStore } from "../domain/identityaccount/store.js";
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: Pick<
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),
@@ -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); or
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
- ? newBuiltInAuthorizer({
449
- store,
450
- policies: iamPolicies,
451
- accounts: identityAccounts,
452
- edition: extensions.edition,
453
- logger,
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
- // The runner's credential in either posture is an operator-minted API
1478
- // token via STIGMER_TOKEN (O3 ruling Q3) — no runner-specific verifier.
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({
@@ -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) => executionContextCommand.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
- { logger: silentLogger, secretService, runnerAuthService: provider },
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
- { logger: silentLogger, secretService, runnerAuthService: provider },
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
- { logger: silentLogger, secretService, runnerAuthService: provider },
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
- { logger: silentLogger, secretService, runnerAuthService: provider },
148
+ {
149
+ logger: silentLogger,
150
+ secretService,
151
+ runnerAuthService: provider,
152
+ store,
153
+ },
125
154
  ctxWithBearer("sandbox-token"),
126
155
  ec,
127
156
  );