@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.
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +4 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +4 -0
- package/dist/{chunk-IBV7Z6F4.js → chunk-QESX7HDK.js} +3645 -470
- package/dist/chunk-QESX7HDK.js.map +1 -0
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/http/sse.d.ts +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +168 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/server.d.ts +33 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/managed-auth-session-sets.d.ts +19 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/sessions.d.ts +17 -5
- package/dist/routes/workspaces.d.ts +1 -0
- package/dist/work-discovery-observability.d.ts +33 -0
- package/package.json +18 -18
- package/src/app.ts +133 -1
- package/src/auth/managed-auth-attempt-context.ts +24 -0
- package/src/auth/managed-auth-session-adapter.ts +205 -0
- package/src/auth/managed-auth.ts +52 -2
- package/src/fatal-process-boundary.ts +231 -0
- package/src/http/sse.ts +7 -0
- package/src/index.ts +25 -5
- package/src/integrations/slack-interactions.ts +30 -17
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/server.ts +349 -68
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/canonical-human-identities.ts +29 -14
- package/src/routes/codex.ts +5 -1
- package/src/routes/environments.ts +23 -0
- package/src/routes/interaction-resources.ts +3 -0
- package/src/routes/managed-auth-session-sets.ts +994 -0
- package/src/routes/managed-onboarding.ts +2 -0
- package/src/routes/organization-memberships.ts +2 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/sessions.ts +401 -120
- package/src/routes/supergrok.ts +5 -1
- package/src/routes/workspaces.ts +23 -1
- package/src/work-discovery-observability.ts +121 -0
- 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 {};
|
package/dist/http/sse.d.ts
CHANGED
|
@@ -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(
|
|
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-
|
|
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 {
|
|
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
|
-
|
|
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") {
|