@opengeni/api-router 2.3.2-canary.2 → 2.5.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 (44) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/managed-auth-attempt-context.d.ts +4 -0
  3. package/dist/auth/managed-auth-session-adapter.d.ts +4 -0
  4. package/dist/{chunk-IBV7Z6F4.js → chunk-QESX7HDK.js} +3645 -470
  5. package/dist/chunk-QESX7HDK.js.map +1 -0
  6. package/dist/fatal-process-boundary.d.ts +25 -0
  7. package/dist/http/sse.d.ts +2 -0
  8. package/dist/index.d.ts +5 -1
  9. package/dist/index.js +168 -6
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/slack-interactions.d.ts +1 -1
  12. package/dist/mcp/receipts.d.ts +9 -0
  13. package/dist/mcp/server.d.ts +33 -0
  14. package/dist/organization-recovery-notifications.d.ts +41 -0
  15. package/dist/routes/managed-auth-session-sets.d.ts +19 -0
  16. package/dist/routes/organization-recovery.d.ts +19 -0
  17. package/dist/routes/sessions.d.ts +17 -5
  18. package/dist/routes/workspaces.d.ts +1 -0
  19. package/dist/work-discovery-observability.d.ts +33 -0
  20. package/package.json +18 -18
  21. package/src/app.ts +133 -1
  22. package/src/auth/managed-auth-attempt-context.ts +24 -0
  23. package/src/auth/managed-auth-session-adapter.ts +205 -0
  24. package/src/auth/managed-auth.ts +52 -2
  25. package/src/fatal-process-boundary.ts +231 -0
  26. package/src/http/sse.ts +7 -0
  27. package/src/index.ts +25 -5
  28. package/src/integrations/slack-interactions.ts +30 -17
  29. package/src/mcp/receipts.ts +34 -0
  30. package/src/mcp/server.ts +349 -68
  31. package/src/organization-recovery-notifications.ts +103 -0
  32. package/src/routes/canonical-human-identities.ts +29 -14
  33. package/src/routes/codex.ts +5 -1
  34. package/src/routes/environments.ts +23 -0
  35. package/src/routes/interaction-resources.ts +3 -0
  36. package/src/routes/managed-auth-session-sets.ts +994 -0
  37. package/src/routes/managed-onboarding.ts +2 -0
  38. package/src/routes/organization-memberships.ts +2 -0
  39. package/src/routes/organization-recovery.ts +325 -0
  40. package/src/routes/sessions.ts +401 -120
  41. package/src/routes/supergrok.ts +5 -1
  42. package/src/routes/workspaces.ts +23 -1
  43. package/src/work-discovery-observability.ts +121 -0
  44. package/dist/chunk-IBV7Z6F4.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ import type { Observability } from "@opengeni/observability";
2
+ export type ApiFatalEvent = "startup_failure" | "unhandled_rejection" | "uncaught_exception";
3
+ export type ApiFatalPhase = "startup" | "running";
4
+ export type ApiFatalReasonKind = "bigint" | "boolean" | "error" | "function" | "null" | "number" | "object" | "string" | "symbol" | "undefined";
5
+ type ApiFatalObservability = Pick<Observability, "error" | "flush" | "startSpan">;
6
+ type ApiFatalProcess = {
7
+ on: (event: "unhandledRejection" | "uncaughtException", listener: (reason: unknown) => void) => void;
8
+ off: (event: "unhandledRejection" | "uncaughtException", listener: (reason: unknown) => void) => void;
9
+ exit: (code: number) => void;
10
+ };
11
+ export type ApiFatalProcessBoundaryOptions = {
12
+ process?: ApiFatalProcess;
13
+ observability?: ApiFatalObservability;
14
+ flushTimeoutMs?: number;
15
+ correlationId?: () => string;
16
+ fallbackLog?: (message: string) => void;
17
+ };
18
+ export type ApiFatalProcessBoundary = {
19
+ attachObservability: (observability: ApiFatalObservability) => void;
20
+ markRunning: () => void;
21
+ reportStartupFailure: (reason: unknown) => Promise<void>;
22
+ dispose: () => void;
23
+ };
24
+ export declare function installApiFatalProcessBoundary(options?: ApiFatalProcessBoundaryOptions): ApiFatalProcessBoundary;
25
+ export {};
@@ -84,5 +84,7 @@ export type SseDeliveryOptions = {
84
84
  /** Current ACL re-check, run even while the event stream is idle. */
85
85
  reauthorize?: (() => Promise<void>) | undefined;
86
86
  reauthorizeAfterMs?: number | undefined;
87
+ /** Exact selected actor emitted on the stream response for cross-tab fencing. */
88
+ actorEpoch?: string | undefined;
87
89
  };
88
90
  export type SessionSseDeliveryOptions = SseDeliveryOptions;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getSettings } from "@opengeni/config";
2
2
  import type { ScheduledTaskOverlapPolicy, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
3
3
  import { type Database } from "@opengeni/db";
4
+ import { type Observability } from "@opengeni/observability";
4
5
  import { ScheduleOverlapPolicy } from "@temporalio/client";
5
6
  import type { ScheduleSpec } from "@temporalio/client";
6
7
  import { type DocumentIndexClient, type SessionWorkflowClient } from "./app.js";
@@ -9,7 +10,10 @@ export declare function createTemporalWorkflowClient(settings: ReturnType<typeof
9
10
  documentIndexer: DocumentIndexClient;
10
11
  close: () => Promise<void>;
11
12
  }>;
12
- export declare function startApi(): Promise<{
13
+ export declare function startApi(options?: {
14
+ settings?: ReturnType<typeof getSettings>;
15
+ observability?: Observability;
16
+ }): Promise<{
13
17
  server: Bun.Server<Readonly<{
14
18
  attach(socket: import("./api-websocket.js").ApiWebSocketLike): void;
15
19
  receive(message: string | Uint8Array | ArrayBuffer): void;
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createOpenGeniSlackBotInteractionClient,
7
7
  startSlackInteractionPump,
8
8
  startTemporalScheduleCleanupPump
9
- } from "./chunk-IBV7Z6F4.js";
9
+ } from "./chunk-QESX7HDK.js";
10
10
 
11
11
  // src/index.ts
12
12
  import {
@@ -26,7 +26,10 @@ import {
26
26
  runtimeDatabaseReadyCheck
27
27
  } from "@opengeni/db";
28
28
  import { createNatsEventBus } from "@opengeni/events";
29
- import { createObservability, logStartupDependencyRetry } from "@opengeni/observability";
29
+ import {
30
+ createObservability,
31
+ logStartupDependencyRetry
32
+ } from "@opengeni/observability";
30
33
  import { createObjectStorage } from "@opengeni/storage";
31
34
  import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
32
35
  import { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from "@opengeni/core";
@@ -2398,6 +2401,156 @@ function rawRows(result) {
2398
2401
  throw new Error("Unsupported database execute result shape");
2399
2402
  }
2400
2403
 
2404
+ // src/fatal-process-boundary.ts
2405
+ import { randomUUID as randomUUID2 } from "crypto";
2406
+ var API_FATAL_FLUSH_TIMEOUT_MS = 1e3;
2407
+ var API_FATAL_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
2408
+ var FATAL_ERROR_CODES = {
2409
+ startup_failure: "api_startup_failed",
2410
+ unhandled_rejection: "api_unhandled_rejection",
2411
+ uncaught_exception: "api_uncaught_exception"
2412
+ };
2413
+ function installApiFatalProcessBoundary(options = {}) {
2414
+ const runtimeProcess = options.process ?? defaultProcessBoundary();
2415
+ const flushTimeoutMs = options.flushTimeoutMs ?? API_FATAL_FLUSH_TIMEOUT_MS;
2416
+ const correlationId = options.correlationId ?? (() => `api-fatal.${randomUUID2()}`);
2417
+ const fallbackLog = options.fallbackLog ?? ((message) => console.error(message));
2418
+ let observability = options.observability;
2419
+ let phase = "startup";
2420
+ let reporting = false;
2421
+ const report = async (event, reason) => {
2422
+ if (reporting) return;
2423
+ reporting = true;
2424
+ const diagnostic = apiFatalDiagnostic(event, phase, reason, correlationId);
2425
+ const message = apiFatalMessage(diagnostic);
2426
+ const activeObservability = observability;
2427
+ try {
2428
+ if (activeObservability) {
2429
+ let logged = false;
2430
+ try {
2431
+ activeObservability.error(message, diagnostic);
2432
+ logged = true;
2433
+ } catch {
2434
+ }
2435
+ if (!logged) {
2436
+ safeFallbackLog(fallbackLog, message);
2437
+ }
2438
+ try {
2439
+ const span = activeObservability.startSpan("api.process.fatal", diagnostic);
2440
+ span.end({ error: true });
2441
+ } catch {
2442
+ }
2443
+ await flushWithin(activeObservability, flushTimeoutMs);
2444
+ } else {
2445
+ safeFallbackLog(fallbackLog, message);
2446
+ }
2447
+ } finally {
2448
+ runtimeProcess.exit(1);
2449
+ }
2450
+ };
2451
+ const onUnhandledRejection = (reason) => {
2452
+ void report("unhandled_rejection", reason);
2453
+ };
2454
+ const onUncaughtException = (reason) => {
2455
+ void report("uncaught_exception", reason);
2456
+ };
2457
+ runtimeProcess.on("unhandledRejection", onUnhandledRejection);
2458
+ runtimeProcess.on("uncaughtException", onUncaughtException);
2459
+ return {
2460
+ attachObservability: (value) => {
2461
+ observability = value;
2462
+ },
2463
+ markRunning: () => {
2464
+ phase = "running";
2465
+ },
2466
+ reportStartupFailure: async (reason) => {
2467
+ await report("startup_failure", reason);
2468
+ },
2469
+ dispose: () => {
2470
+ runtimeProcess.off("unhandledRejection", onUnhandledRejection);
2471
+ runtimeProcess.off("uncaughtException", onUncaughtException);
2472
+ }
2473
+ };
2474
+ }
2475
+ function apiFatalDiagnostic(event, phase, reason, correlationId) {
2476
+ let safeCorrelationId = "api-fatal.fallback";
2477
+ try {
2478
+ const candidate = correlationId();
2479
+ if (API_FATAL_CORRELATION_ID_PATTERN.test(candidate)) {
2480
+ safeCorrelationId = candidate;
2481
+ }
2482
+ } catch {
2483
+ }
2484
+ return {
2485
+ errorClass: "ApiFatalOperationError",
2486
+ errorCode: FATAL_ERROR_CODES[event],
2487
+ origin: "api",
2488
+ phase,
2489
+ reasonKind: apiFatalReasonKind(reason),
2490
+ correlationId: safeCorrelationId
2491
+ };
2492
+ }
2493
+ function apiFatalReasonKind(reason) {
2494
+ if (reason === null) return "null";
2495
+ const kind = typeof reason;
2496
+ if (kind !== "object") return kind;
2497
+ try {
2498
+ return reason instanceof Error ? "error" : "object";
2499
+ } catch {
2500
+ return "object";
2501
+ }
2502
+ }
2503
+ function apiFatalMessage(diagnostic) {
2504
+ return `OpenGeni API fatal process failure (${diagnostic.errorCode}; phase=${diagnostic.phase}; reason_kind=${diagnostic.reasonKind}; correlation_id=${diagnostic.correlationId})`;
2505
+ }
2506
+ async function flushWithin(observability, timeoutMs) {
2507
+ let flush;
2508
+ try {
2509
+ flush = Promise.resolve(observability.flush()).catch(() => void 0);
2510
+ } catch {
2511
+ return;
2512
+ }
2513
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return;
2514
+ let timeout;
2515
+ try {
2516
+ await Promise.race([
2517
+ flush,
2518
+ new Promise((resolve) => {
2519
+ timeout = setTimeout(resolve, timeoutMs);
2520
+ })
2521
+ ]);
2522
+ } finally {
2523
+ if (timeout !== void 0) clearTimeout(timeout);
2524
+ }
2525
+ }
2526
+ function safeFallbackLog(fallbackLog, message) {
2527
+ try {
2528
+ fallbackLog(message);
2529
+ } catch {
2530
+ }
2531
+ }
2532
+ function defaultProcessBoundary() {
2533
+ return {
2534
+ on: (event, listener) => {
2535
+ if (event === "unhandledRejection") {
2536
+ process.on("unhandledRejection", listener);
2537
+ } else {
2538
+ process.on("uncaughtException", listener);
2539
+ }
2540
+ },
2541
+ off: (event, listener) => {
2542
+ if (event === "unhandledRejection") {
2543
+ process.off("unhandledRejection", listener);
2544
+ } else {
2545
+ process.off("uncaughtException", listener);
2546
+ }
2547
+ },
2548
+ exit: (code) => {
2549
+ process.exit(code);
2550
+ }
2551
+ };
2552
+ }
2553
+
2401
2554
  // src/index.ts
2402
2555
  function isWorkflowAlreadyStarted(error) {
2403
2556
  return error instanceof WorkflowExecutionAlreadyStartedError;
@@ -2620,9 +2773,9 @@ async function createTemporalWorkflowClient(settings, db) {
2620
2773
  }
2621
2774
  };
2622
2775
  }
2623
- async function startApi() {
2624
- const settings = getSettings();
2625
- const observability = createObservability(settings, { component: "api" });
2776
+ async function startApi(options = {}) {
2777
+ const settings = options.settings ?? getSettings();
2778
+ const observability = options.observability ?? createObservability(settings, { component: "api" });
2626
2779
  const searchPath = dbSearchPath(settings);
2627
2780
  const dbClient = createDb(settings.databaseUrl, {
2628
2781
  max: 32,
@@ -2814,7 +2967,16 @@ async function startApi() {
2814
2967
  };
2815
2968
  }
2816
2969
  if (import.meta.main) {
2817
- await startApi();
2970
+ const fatalBoundary = installApiFatalProcessBoundary();
2971
+ try {
2972
+ const settings = getSettings();
2973
+ const observability = createObservability(settings, { component: "api" });
2974
+ fatalBoundary.attachObservability(observability);
2975
+ await startApi({ settings, observability });
2976
+ fatalBoundary.markRunning();
2977
+ } catch (error) {
2978
+ await fatalBoundary.reportStartupFailure(error);
2979
+ }
2818
2980
  }
2819
2981
  function temporalOverlapPolicy(policy) {
2820
2982
  if (policy === "skip") {