@stigmer/server 3.30.0 → 3.32.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.
Files changed (111) hide show
  1. package/README.md +1 -1
  2. package/dist/authorization/model/data/authorization-model.json +16 -46
  3. package/dist/boot/compose.d.ts.map +1 -1
  4. package/dist/boot/compose.js +8 -0
  5. package/dist/boot/compose.js.map +1 -1
  6. package/dist/boot/config.d.ts +10 -0
  7. package/dist/boot/config.d.ts.map +1 -1
  8. package/dist/boot/config.js +2 -0
  9. package/dist/boot/config.js.map +1 -1
  10. package/dist/domain/agentexecution/create-execution-context-step.d.ts +7 -0
  11. package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
  12. package/dist/domain/agentexecution/create-execution-context-step.js +79 -4
  13. package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
  14. package/dist/domain/iampolicy/constants.d.ts +11 -1
  15. package/dist/domain/iampolicy/constants.d.ts.map +1 -1
  16. package/dist/domain/iampolicy/constants.js +11 -1
  17. package/dist/domain/iampolicy/constants.js.map +1 -1
  18. package/dist/domain/iampolicy/controller.d.ts.map +1 -1
  19. package/dist/domain/iampolicy/controller.js +36 -12
  20. package/dist/domain/iampolicy/controller.js.map +1 -1
  21. package/dist/domain/iampolicy/steps.d.ts +98 -0
  22. package/dist/domain/iampolicy/steps.d.ts.map +1 -1
  23. package/dist/domain/iampolicy/steps.js +119 -4
  24. package/dist/domain/iampolicy/steps.js.map +1 -1
  25. package/dist/domain/oauthapp/controller.d.ts +16 -8
  26. package/dist/domain/oauthapp/controller.d.ts.map +1 -1
  27. package/dist/domain/oauthapp/controller.js +28 -11
  28. package/dist/domain/oauthapp/controller.js.map +1 -1
  29. package/dist/domain/oauthapp/refresolution.d.ts.map +1 -1
  30. package/dist/domain/oauthapp/refresolution.js +3 -2
  31. package/dist/domain/oauthapp/refresolution.js.map +1 -1
  32. package/dist/domain/oauthapp/steps.d.ts +5 -5
  33. package/dist/domain/oauthapp/steps.js +6 -6
  34. package/dist/domain/oauthapp/steps.js.map +1 -1
  35. package/dist/domain/platformclient/constants.d.ts +11 -1
  36. package/dist/domain/platformclient/constants.d.ts.map +1 -1
  37. package/dist/domain/platformclient/constants.js +11 -1
  38. package/dist/domain/platformclient/constants.js.map +1 -1
  39. package/dist/domain/platformclient/verifier.d.ts.map +1 -1
  40. package/dist/domain/platformclient/verifier.js +7 -2
  41. package/dist/domain/platformclient/verifier.js.map +1 -1
  42. package/dist/extensions/identity.d.ts +14 -0
  43. package/dist/extensions/identity.d.ts.map +1 -1
  44. package/dist/extensions/identity.js.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/pipeline/steps/defaults.d.ts +5 -1
  50. package/dist/pipeline/steps/defaults.d.ts.map +1 -1
  51. package/dist/pipeline/steps/defaults.js +6 -1
  52. package/dist/pipeline/steps/defaults.js.map +1 -1
  53. package/dist/sandbox/docker.d.ts.map +1 -1
  54. package/dist/sandbox/docker.js +13 -5
  55. package/dist/sandbox/docker.js.map +1 -1
  56. package/dist/sandbox/kubernetes.d.ts +1 -1
  57. package/dist/sandbox/kubernetes.d.ts.map +1 -1
  58. package/dist/sandbox/kubernetes.js +18 -4
  59. package/dist/sandbox/kubernetes.js.map +1 -1
  60. package/dist/sandbox/local-process.d.ts.map +1 -1
  61. package/dist/sandbox/local-process.js +8 -1
  62. package/dist/sandbox/local-process.js.map +1 -1
  63. package/dist/sandbox/provisioner.d.ts +12 -0
  64. package/dist/sandbox/provisioner.d.ts.map +1 -1
  65. package/dist/sandbox/provisioner.js.map +1 -1
  66. package/dist/temporal/manager.d.ts +10 -0
  67. package/dist/temporal/manager.d.ts.map +1 -1
  68. package/dist/temporal/manager.js +10 -0
  69. package/dist/temporal/manager.js.map +1 -1
  70. package/package.json +6 -6
  71. package/src/authorization/__tests__/store-tests.test.ts +1 -0
  72. package/src/authorization/model/data/authorization-model.json +16 -46
  73. package/src/boot/__tests__/config.test.ts +23 -1
  74. package/src/boot/compose.ts +8 -0
  75. package/src/boot/config.ts +13 -0
  76. package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +329 -0
  77. package/src/domain/agentexecution/__tests__/lifecycle.test.ts +7 -0
  78. package/src/domain/agentexecution/create-execution-context-step.ts +110 -4
  79. package/src/domain/iampolicy/__tests__/controller.test.ts +99 -3
  80. package/src/domain/iampolicy/__tests__/steps.test.ts +179 -0
  81. package/src/domain/iampolicy/constants.ts +15 -1
  82. package/src/domain/iampolicy/controller.ts +43 -9
  83. package/src/domain/iampolicy/steps.ts +164 -4
  84. package/src/domain/oauthapp/__tests__/oauthapp.test.ts +17 -10
  85. package/src/domain/oauthapp/controller.ts +51 -18
  86. package/src/domain/oauthapp/refresolution.ts +3 -2
  87. package/src/domain/oauthapp/steps.ts +6 -6
  88. package/src/domain/platformclient/__tests__/mint.test.ts +7 -1
  89. package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +4 -2
  90. package/src/domain/platformclient/constants.ts +11 -1
  91. package/src/domain/platformclient/verifier.ts +7 -2
  92. package/src/extensions/__tests__/role-reconciliation-composed.test.ts +18 -10
  93. package/src/extensions/identity.ts +14 -0
  94. package/src/index.ts +5 -0
  95. package/src/pipeline/__tests__/steps.test.ts +72 -1
  96. package/src/pipeline/steps/defaults.ts +6 -1
  97. package/src/sandbox/__tests__/docker.integration.test.ts +2 -0
  98. package/src/sandbox/__tests__/kubernetes.test.ts +27 -0
  99. package/src/sandbox/__tests__/local-process.integration.test.ts +10 -0
  100. package/src/sandbox/__tests__/provisioner.test.ts +2 -0
  101. package/src/sandbox/docker.ts +11 -5
  102. package/src/sandbox/kubernetes.ts +18 -3
  103. package/src/sandbox/local-process.ts +11 -1
  104. package/src/sandbox/provisioner.ts +12 -0
  105. package/src/temporal/__tests__/manager.test.ts +59 -1
  106. package/src/temporal/manager.ts +13 -0
  107. package/dist/domain/oauthapp/refresolution/refresolution.d.ts +0 -12
  108. package/dist/domain/oauthapp/refresolution/refresolution.d.ts.map +0 -1
  109. package/dist/domain/oauthapp/refresolution/refresolution.js +0 -79
  110. package/dist/domain/oauthapp/refresolution/refresolution.js.map +0 -1
  111. package/src/domain/oauthapp/refresolution/refresolution.ts +0 -94
@@ -6,7 +6,8 @@
6
6
  * @kubernetes/client-node, the official client — this driver is the one
7
7
  * the cloud composition will eventually run in production, C4).
8
8
  *
9
- * Each sandbox = a per-sandbox Secret (STIGMER_TOKEN) + a single-replica
9
+ * Each sandbox = a per-sandbox Secret (STIGMER_TOKEN, plus the Temporal
10
+ * connection settings when the server's Temporal is authenticated) + a single-replica
10
11
  * Deployment (strategy Recreate) + a PVC-backed /workspace for the
11
12
  * persistent scopes (session, workflow — the Java persistent-scope
12
13
  * split; connect rides emptyDir). One shared namespace, never
@@ -255,6 +256,7 @@ export function buildSandboxSecret(
255
256
  scope: SandboxScope,
256
257
  id: string,
257
258
  stigmerToken: string,
259
+ temporalConnectionEnv: Readonly<Record<string, string>> = {},
258
260
  ): V1Secret {
259
261
  return {
260
262
  metadata: {
@@ -264,7 +266,10 @@ export function buildSandboxSecret(
264
266
  type: "Opaque",
265
267
  // A token-less sandbox still gets its (empty) Secret — the object
266
268
  // must exist for uniform cleanup (the Java posture).
267
- stringData: stigmerToken !== "" ? { STIGMER_TOKEN: stigmerToken } : {},
269
+ stringData: {
270
+ ...temporalConnectionEnv,
271
+ ...(stigmerToken !== "" ? { STIGMER_TOKEN: stigmerToken } : {}),
272
+ },
268
273
  };
269
274
  }
270
275
 
@@ -308,6 +313,7 @@ export function buildSandboxDeployment(
308
313
  { name: "STIGMER_TASK_QUEUE", value: env.taskQueue },
309
314
  { name: "STIGMER_BACKEND_ENDPOINT", value: config.backendEndpoint },
310
315
  { name: "TEMPORAL_SERVICE_ADDRESS", value: config.temporalAddress },
316
+ { name: "TEMPORAL_NAMESPACE", value: config.temporalNamespace },
311
317
  { name: "WORKSPACE_ROOT_DIR", value: WORKSPACE_MOUNT_PATH },
312
318
  ];
313
319
  if (env.stigmerToken !== "") {
@@ -318,6 +324,15 @@ export function buildSandboxDeployment(
318
324
  },
319
325
  });
320
326
  }
327
+ // The Temporal connection settings sit in the same per-sandbox Secret as
328
+ // the token (buildSandboxSecret): they carry credentials, so the
329
+ // Deployment names them and never holds their values.
330
+ for (const name of Object.keys(config.temporalConnectionEnv)) {
331
+ containerEnv.push({
332
+ name,
333
+ valueFrom: { secretKeyRef: { name: `${baseName}-env`, key: name } },
334
+ });
335
+ }
321
336
 
322
337
  return {
323
338
  metadata: { name: baseName, labels },
@@ -417,7 +432,7 @@ export function newKubernetesSandboxProvisionerOverGateway(
417
432
  // references it), claim (reused when it survived a repair), then
418
433
  // the Deployment. No readiness wait (module header).
419
434
  await gateway.applySecret(
420
- buildSandboxSecret(scope, id, env.stigmerToken),
435
+ buildSandboxSecret(scope, id, env.stigmerToken, config.temporalConnectionEnv),
421
436
  );
422
437
  if (PERSISTENT_SCOPES.has(scope)) {
423
438
  await gateway.applyPvc(buildWorkspacePvc(scope, id));
@@ -33,6 +33,8 @@ import { mkdirSync } from "node:fs";
33
33
  import { homedir } from "node:os";
34
34
  import path from "node:path";
35
35
 
36
+ import { TEMPORAL_CONNECTION_ENV_NAMES } from "@stigmer/temporal-codecs";
37
+
36
38
  import type { Logger } from "../boot/logger.js";
37
39
  import type {
38
40
  SandboxEnvironment,
@@ -86,10 +88,18 @@ export const newLocalProcessSandboxProvisioner: SandboxProvisionerFactory = ({
86
88
  );
87
89
  mkdirSync(workspaceRoot, { recursive: true });
88
90
 
91
+ // The server's own Temporal connection settings are replaced by their
92
+ // rendered form, never inherited beside it: an inherited _PATH item
93
+ // next to its rendered _DATA twin is a boot refusal in the runner.
94
+ const inherited = Object.fromEntries(
95
+ Object.entries(process.env).filter(([name]) => !TEMPORAL_CONNECTION_ENV_NAMES.includes(name)),
96
+ );
89
97
  const childEnv: NodeJS.ProcessEnv = {
90
- ...process.env,
98
+ ...inherited,
99
+ ...config.temporalConnectionEnv,
91
100
  MODE: "local",
92
101
  STIGMER_TASK_QUEUE: env.taskQueue,
102
+ TEMPORAL_NAMESPACE: config.temporalNamespace,
93
103
  WORKSPACE_ROOT_DIR: workspaceRoot,
94
104
  };
95
105
  if (config.backendEndpoint !== "") {
@@ -136,6 +136,18 @@ export interface SandboxDriverConfig {
136
136
  readonly backendEndpoint: string;
137
137
  /** Temporal address as reachable from inside a sandbox (TEMPORAL_SERVICE_ADDRESS). */
138
138
  readonly temporalAddress: string;
139
+ /** The Temporal namespace the sandbox's runner polls in (TEMPORAL_NAMESPACE). */
140
+ readonly temporalNamespace: string;
141
+ /**
142
+ * How the sandbox's runner authenticates to that Temporal: the server's
143
+ * own `STIGMER_TEMPORAL_*` settings rendered by `temporalConnectionEnv`
144
+ * (`@stigmer/temporal-codecs`), every PEM item in its `_DATA` form so no
145
+ * file has to exist inside the sandbox. Empty for a plaintext Temporal.
146
+ * The values include credentials, so each driver passes them through the
147
+ * channel it uses for the token, never through argv or a plain manifest
148
+ * value.
149
+ */
150
+ readonly temporalConnectionEnv: Readonly<Record<string, string>>;
139
151
  /** The runner image for container-based drivers (docker/kubernetes). */
140
152
  readonly runnerImage: string;
141
153
  /** The runner executable for the local-process driver. */
@@ -11,6 +11,9 @@
11
11
  * lives forever — latent until #21/#22 add factories);
12
12
  * - the Go availability parity: getClient() is undefined only until
13
13
  * the first successful connect;
14
+ * - both connections, the client's and the workers' native one, carry
15
+ * the configured connection security (TLS and API key) beside the
16
+ * address, and a plaintext manager passes neither;
14
17
  * - deps.createWorker builds through THIS package's Worker.create with
15
18
  * the manager's connection, namespace, and codec chain pre-wired
16
19
  * (the finding-16 seam — factories never see the NativeConnection);
@@ -34,14 +37,34 @@ const sdkSpy = vi.hoisted(() => ({
34
37
  createCalls: [] as Record<string, unknown>[],
35
38
  /** The connection object the mocked NativeConnection.connect returned last. */
36
39
  lastConnection: undefined as unknown,
40
+ /** Options of every NativeConnection.connect and Connection.connect call. */
41
+ nativeConnectCalls: [] as Record<string, unknown>[],
42
+ clientConnectCalls: [] as Record<string, unknown>[],
37
43
  }));
38
44
 
45
+ vi.mock("@temporalio/client", async (importOriginal) => {
46
+ const original = await importOriginal<typeof import("@temporalio/client")>();
47
+ return {
48
+ ...original,
49
+ Connection: {
50
+ connect: async (options: Record<string, unknown>) => {
51
+ sdkSpy.clientConnectCalls.push(options);
52
+ return { close: async () => {}, healthService: { check: async () => ({ status: 1 }) } };
53
+ },
54
+ },
55
+ Client: class {
56
+ constructor(readonly options: Record<string, unknown>) {}
57
+ },
58
+ };
59
+ });
60
+
39
61
  vi.mock("@temporalio/worker", async (importOriginal) => {
40
62
  const original = await importOriginal<typeof import("@temporalio/worker")>();
41
63
  return {
42
64
  ...original,
43
65
  NativeConnection: {
44
- connect: async () => {
66
+ connect: async (options: Record<string, unknown>) => {
67
+ sdkSpy.nativeConnectCalls.push(options);
45
68
  const connection = { close: async () => {} };
46
69
  sdkSpy.lastConnection = connection;
47
70
  return connection;
@@ -74,6 +97,8 @@ const managers: TemporalManager[] = [];
74
97
  afterEach(async () => {
75
98
  sdkSpy.createCalls.length = 0;
76
99
  sdkSpy.lastConnection = undefined;
100
+ sdkSpy.nativeConnectCalls.length = 0;
101
+ sdkSpy.clientConnectCalls.length = 0;
77
102
  for (const manager of managers.splice(0)) {
78
103
  await manager.close();
79
104
  }
@@ -108,11 +133,13 @@ function newManager(
108
133
  payloadCodecs?: ConstructorParameters<
109
134
  typeof TemporalManager
110
135
  >[0]["payloadCodecs"];
136
+ connection?: ConstructorParameters<typeof TemporalManager>[0]["connection"];
111
137
  },
112
138
  ): TemporalManager {
113
139
  const manager = new TemporalManager({
114
140
  hostPort: "127.0.0.1:1",
115
141
  namespace: "default",
142
+ connection: options?.connection ?? {},
116
143
  logger: options?.logger ?? silentLogger,
117
144
  payloadCodecs: options?.payloadCodecs ?? [],
118
145
  workerFactories: factories,
@@ -283,6 +310,37 @@ describe("TemporalManager createWorker capability (the finding-16 seam)", () =>
283
310
  });
284
311
  });
285
312
 
313
+ describe("TemporalManager connection security", () => {
314
+ it("spreads TLS and the API key into both the client's and the workers' connections", async () => {
315
+ const security = {
316
+ tls: { serverNameOverride: "temporal.internal" },
317
+ apiKey: "api-key-1",
318
+ };
319
+ const manager = newManager([fakeWorkerFactory({ shutdowns: 0 })], {
320
+ connection: security,
321
+ });
322
+
323
+ await manager.initialConnect();
324
+ await manager.startWorkers();
325
+
326
+ expect(sdkSpy.clientConnectCalls).toHaveLength(1);
327
+ expect(sdkSpy.clientConnectCalls[0]).toMatchObject({ address: "127.0.0.1:1", ...security });
328
+ expect(sdkSpy.nativeConnectCalls).toHaveLength(1);
329
+ expect(sdkSpy.nativeConnectCalls[0]).toEqual({ address: "127.0.0.1:1", ...security });
330
+ });
331
+
332
+ it("passes neither for a plaintext manager", async () => {
333
+ const manager = newManager([fakeWorkerFactory({ shutdowns: 0 })]);
334
+
335
+ await manager.initialConnect();
336
+ await manager.startWorkers();
337
+
338
+ expect(sdkSpy.clientConnectCalls[0]).not.toHaveProperty("tls");
339
+ expect(sdkSpy.clientConnectCalls[0]).not.toHaveProperty("apiKey");
340
+ expect(sdkSpy.nativeConnectCalls[0]).toEqual({ address: "127.0.0.1:1" });
341
+ });
342
+ });
343
+
286
344
  describe("TemporalManager worker-death observability", () => {
287
345
  it("logs a run() rejection at ERROR with the dead worker's queue identity", async () => {
288
346
  const lines: string[] = [];
@@ -32,6 +32,12 @@
32
32
  * connection errors exactly as Go's stale-client operations do, and the
33
33
  * health monitor swaps in a fresh client when the server returns.
34
34
  *
35
+ * One choke point for connection security: `connection` (TLS, mutual TLS,
36
+ * an API key; `@stigmer/temporal-codecs`' `loadTemporalConnectionConfig`)
37
+ * is spread into BOTH connects, the client's and the workers' native one,
38
+ * so every worker, OSS and extension alike, authenticates the same way
39
+ * through createWorker. Absent settings keep the plaintext connection.
40
+ *
35
41
  * One choke point for payload decryption: the codecs are installed on the
36
42
  * client at dial AND wired into every worker by the deps' createWorker
37
43
  * capability (the TS SDK's Worker.create takes its own dataConverter; Go
@@ -40,6 +46,7 @@
40
46
  */
41
47
  import { Client, Connection } from "@temporalio/client";
42
48
  import type { PayloadCodec } from "@temporalio/common";
49
+ import type { TemporalConnectionConfig } from "@stigmer/temporal-codecs";
43
50
  import {
44
51
  NativeConnection,
45
52
  Worker,
@@ -150,6 +157,8 @@ export type WorkerFactory = (deps: WorkerFactoryDeps) => Promise<Worker>;
150
157
  export interface TemporalManagerOptions {
151
158
  readonly hostPort: string;
152
159
  readonly namespace: string;
160
+ /** TLS and API key for both connections; `{}` for plaintext. */
161
+ readonly connection: TemporalConnectionConfig;
153
162
  readonly logger: Logger;
154
163
  readonly payloadCodecs: PayloadCodec[];
155
164
  readonly workerFactories: readonly WorkerFactory[];
@@ -163,6 +172,7 @@ interface RunningWorker {
163
172
  export class TemporalManager {
164
173
  private readonly hostPort: string;
165
174
  private readonly namespace: string;
175
+ private readonly connectionSecurity: TemporalConnectionConfig;
166
176
  private readonly logger: Logger;
167
177
  private readonly payloadCodecs: PayloadCodec[];
168
178
  private readonly workerFactories: readonly WorkerFactory[];
@@ -184,6 +194,7 @@ export class TemporalManager {
184
194
  constructor(options: TemporalManagerOptions) {
185
195
  this.hostPort = options.hostPort;
186
196
  this.namespace = options.namespace;
197
+ this.connectionSecurity = options.connection;
187
198
  this.logger = options.logger;
188
199
  this.payloadCodecs = options.payloadCodecs;
189
200
  this.workerFactories = options.workerFactories;
@@ -328,6 +339,7 @@ export class TemporalManager {
328
339
  const connection = await Connection.connect({
329
340
  address: this.hostPort,
330
341
  connectTimeout: DIAL_CONNECT_TIMEOUT_MS,
342
+ ...this.connectionSecurity,
331
343
  });
332
344
  const client = new Client({
333
345
  connection,
@@ -494,6 +506,7 @@ export class TemporalManager {
494
506
  private async createAndRunWorkers(): Promise<void> {
495
507
  const nativeConnection = await NativeConnection.connect({
496
508
  address: this.hostPort,
509
+ ...this.connectionSecurity,
497
510
  });
498
511
  this.nativeConnection = nativeConnection;
499
512
 
@@ -1,12 +0,0 @@
1
- import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
2
- import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
3
- import type { Logger } from "../../../boot/logger.js";
4
- import type { Store } from "../../../store/interface.js";
5
- /**
6
- * Resolves the OAuthApp the ref means (Go refresolution.Resolve). A ref
7
- * without a slug resolves to nothing — an McpServerAuth with no (or an
8
- * empty) oauth_app_ref is the DCR/manual-token arm, not a lookup. Store
9
- * errors propagate to the caller (Go returns them).
10
- */
11
- export declare function resolveOAuthAppRef(store: Store, ref: ApiResourceReference, logger: Logger): Promise<OAuthApp | undefined>;
12
- //# sourceMappingURL=refresolution.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"refresolution.d.ts","sourceRoot":"","sources":["../../../../src/domain/oauthapp/refresolution/refresolution.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAC;AAGjG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mDAAmD,CAAC;AAElF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEzD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CA2C/B"}
@@ -1,79 +0,0 @@
1
- /**
2
- * OAuthApp ref resolution — ports pkg/domain/oauthapp/refresolution: the
3
- * ONE answer to "which OAuthApp does this `oauth_app_ref` mean?" (the
4
- * defaultinstance pattern — one small module holds a cross-domain semantic
5
- * so its consumers cannot drift apart, stigmer/stigmer#584).
6
- *
7
- * Consumers: the OAuthApp delete guard (this domain, #13); the OAuth
8
- * initiate path, the read-path oauth_status enricher, and the token-refresh
9
- * path arrive with the mcpserver connect/OAuth sub-project (#19) and MUST
10
- * route through this function — three hand-rolled divergent answers are
11
- * exactly what #584 removed.
12
- *
13
- * Resolution semantics (OSS has a flat OAuthApp store — no org-override
14
- * chain like the cloud's OAuthAppResolutionService, so the ref is the whole
15
- * resolution): matching is by slug, with the ref's org as a preference
16
- * rather than a gate:
17
- *
18
- * 1. An exact (org, slug) match wins — unique by the create pipeline's
19
- * duplicate check.
20
- * 2. Otherwise a slug-only match is honored when it is UNIQUE. This lets
21
- * a deployment satisfy a public server's ref pinned to
22
- * `org: stigmer` with an OAuthApp applied in the user's own org (#584).
23
- * 3. Two or more slug matches with no exact hit resolve to nothing, with
24
- * a WARN naming the candidate orgs: ambiguity is never silently
25
- * collapsed into a credential pick.
26
- *
27
- * Returns undefined when nothing resolves; callers own the severity of that
28
- * outcome (the delete guard treats it as unreferenced; initiate will refuse
29
- * NOT_FOUND, refresh will error — #19).
30
- */
31
- import { fromBinary } from "@bufbuild/protobuf";
32
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
33
- import { OAuthAppSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
34
- /**
35
- * Resolves the OAuthApp the ref means (Go refresolution.Resolve). A ref
36
- * without a slug resolves to nothing — an McpServerAuth with no (or an
37
- * empty) oauth_app_ref is the DCR/manual-token arm, not a lookup. Store
38
- * errors propagate to the caller (Go returns them).
39
- */
40
- export async function resolveOAuthAppRef(store, ref, logger) {
41
- if (ref.slug === "") {
42
- return undefined;
43
- }
44
- const rows = await store.listResources(ApiResourceKind.oauth_app);
45
- // Collect-then-decide: a single pass gathers every slug match so the
46
- // outcome depends only on the store's contents, never on its iteration
47
- // order (Go's earlier first-match-wins scan was nondeterministic when a
48
- // slug existed in several orgs).
49
- const slugMatches = [];
50
- for (const data of rows) {
51
- let app;
52
- try {
53
- app = fromBinary(OAuthAppSchema, data);
54
- }
55
- catch {
56
- continue; // skip malformed rows, as Go does
57
- }
58
- if ((app.metadata?.slug ?? "") !== ref.slug) {
59
- continue;
60
- }
61
- if (ref.org !== "" && app.metadata?.org === ref.org) {
62
- return app; // exact match: unique by the create duplicate check
63
- }
64
- slugMatches.push(app);
65
- }
66
- if (slugMatches.length === 0) {
67
- return undefined;
68
- }
69
- if (slugMatches.length === 1) {
70
- return slugMatches[0];
71
- }
72
- logger.warn("oauth_app_ref is ambiguous (slug exists in several orgs, none matching the ref); refusing to pick — pin the ref's org to resolve", {
73
- oauthAppSlug: ref.slug,
74
- refOrg: ref.org,
75
- candidateOrgs: slugMatches.map((app) => app.metadata?.org ?? ""),
76
- });
77
- return undefined;
78
- }
79
- //# sourceMappingURL=refresolution.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"refresolution.js","sourceRoot":"","sources":["../../../../src/domain/oauthapp/refresolution/refresolution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AACtH,OAAO,EAAE,cAAc,EAAE,MAAM,mDAAmD,CAAC;AAMnF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAY,EACZ,GAAyB,EACzB,MAAc;IAEd,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAElE,qEAAqE;IACrE,uEAAuE;IACvE,wEAAwE;IACxE,iCAAiC;IACjC,MAAM,WAAW,GAAe,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,UAAU,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,kCAAkC;QAC9C,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;YACpD,OAAO,GAAG,CAAC,CAAC,oDAAoD;QAClE,CAAC;QACD,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,CAAC,IAAI,CACT,kIAAkI,EAClI;QACE,YAAY,EAAE,GAAG,CAAC,IAAI;QACtB,MAAM,EAAE,GAAG,CAAC,GAAG;QACf,aAAa,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC;KACjE,CACF,CAAC;IACF,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,94 +0,0 @@
1
- /**
2
- * OAuthApp ref resolution — ports pkg/domain/oauthapp/refresolution: the
3
- * ONE answer to "which OAuthApp does this `oauth_app_ref` mean?" (the
4
- * defaultinstance pattern — one small module holds a cross-domain semantic
5
- * so its consumers cannot drift apart, stigmer/stigmer#584).
6
- *
7
- * Consumers: the OAuthApp delete guard (this domain, #13); the OAuth
8
- * initiate path, the read-path oauth_status enricher, and the token-refresh
9
- * path arrive with the mcpserver connect/OAuth sub-project (#19) and MUST
10
- * route through this function — three hand-rolled divergent answers are
11
- * exactly what #584 removed.
12
- *
13
- * Resolution semantics (OSS has a flat OAuthApp store — no org-override
14
- * chain like the cloud's OAuthAppResolutionService, so the ref is the whole
15
- * resolution): matching is by slug, with the ref's org as a preference
16
- * rather than a gate:
17
- *
18
- * 1. An exact (org, slug) match wins — unique by the create pipeline's
19
- * duplicate check.
20
- * 2. Otherwise a slug-only match is honored when it is UNIQUE. This lets
21
- * a deployment satisfy a public server's ref pinned to
22
- * `org: stigmer` with an OAuthApp applied in the user's own org (#584).
23
- * 3. Two or more slug matches with no exact hit resolve to nothing, with
24
- * a WARN naming the candidate orgs: ambiguity is never silently
25
- * collapsed into a credential pick.
26
- *
27
- * Returns undefined when nothing resolves; callers own the severity of that
28
- * outcome (the delete guard treats it as unreferenced; initiate will refuse
29
- * NOT_FOUND, refresh will error — #19).
30
- */
31
- import { fromBinary } from "@bufbuild/protobuf";
32
-
33
- import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
34
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
35
- import { OAuthAppSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
36
- import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
37
-
38
- import type { Logger } from "../../../boot/logger.js";
39
- import type { Store } from "../../../store/interface.js";
40
-
41
- /**
42
- * Resolves the OAuthApp the ref means (Go refresolution.Resolve). A ref
43
- * without a slug resolves to nothing — an McpServerAuth with no (or an
44
- * empty) oauth_app_ref is the DCR/manual-token arm, not a lookup. Store
45
- * errors propagate to the caller (Go returns them).
46
- */
47
- export async function resolveOAuthAppRef(
48
- store: Store,
49
- ref: ApiResourceReference,
50
- logger: Logger,
51
- ): Promise<OAuthApp | undefined> {
52
- if (ref.slug === "") {
53
- return undefined;
54
- }
55
-
56
- const rows = await store.listResources(ApiResourceKind.oauth_app);
57
-
58
- // Collect-then-decide: a single pass gathers every slug match so the
59
- // outcome depends only on the store's contents, never on its iteration
60
- // order (Go's earlier first-match-wins scan was nondeterministic when a
61
- // slug existed in several orgs).
62
- const slugMatches: OAuthApp[] = [];
63
- for (const data of rows) {
64
- let app: OAuthApp;
65
- try {
66
- app = fromBinary(OAuthAppSchema, data);
67
- } catch {
68
- continue; // skip malformed rows, as Go does
69
- }
70
- if ((app.metadata?.slug ?? "") !== ref.slug) {
71
- continue;
72
- }
73
- if (ref.org !== "" && app.metadata?.org === ref.org) {
74
- return app; // exact match: unique by the create duplicate check
75
- }
76
- slugMatches.push(app);
77
- }
78
-
79
- if (slugMatches.length === 0) {
80
- return undefined;
81
- }
82
- if (slugMatches.length === 1) {
83
- return slugMatches[0];
84
- }
85
- logger.warn(
86
- "oauth_app_ref is ambiguous (slug exists in several orgs, none matching the ref); refusing to pick — pin the ref's org to resolve",
87
- {
88
- oauthAppSlug: ref.slug,
89
- refOrg: ref.org,
90
- candidateOrgs: slugMatches.map((app) => app.metadata?.org ?? ""),
91
- },
92
- );
93
- return undefined;
94
- }