@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
@@ -80,7 +80,11 @@ async function managedCookieHuman(
80
80
  ) {
81
81
  return null;
82
82
  }
83
- const session = await getManagedSession(c, deps.managedAuth);
83
+ const session = await getManagedSession(c, deps.managedAuth, {
84
+ db: deps.db,
85
+ sessionAdapter: deps.managedAuthSessionAdapter,
86
+ sessionSetMode: deps.settings.managedAuthSessionSetMode,
87
+ });
84
88
  return session?.user?.id ? { subjectId: `user:${session.user.id}` } : null;
85
89
  }
86
90
 
@@ -45,6 +45,7 @@ import { boundWorkspaceControlHttpPage } from "@opengeni/events";
45
45
  import type { Hono } from "hono";
46
46
  import { HTTPException } from "hono/http-exception";
47
47
  import {
48
+ getManagedAuthRequestActorEpoch,
48
49
  hasPermission,
49
50
  requireAccessContext,
50
51
  requireAccessGrant,
@@ -58,6 +59,7 @@ import {
58
59
  resolveMemberSubjectId,
59
60
  } from "@opengeni/core";
60
61
  import { boundedLimit } from "../http/common";
62
+ import { ApiHttpError } from "../http/api-error";
61
63
  import { sseWorkspaceControlStream } from "../http/sse";
62
64
  import { buildWorkspaceModelCatalog } from "../model-catalog";
63
65
  import { processTemporalScheduleCleanupClaims } from "../temporal-schedule-cleanup";
@@ -97,6 +99,14 @@ export function workspaceMembersResponse(members: readonly WorkspaceMemberProjec
97
99
  });
98
100
  }
99
101
 
102
+ export function workspaceUpdateRequestsAccountTransfer(value: unknown): boolean {
103
+ return (
104
+ typeof value === "object" &&
105
+ value !== null &&
106
+ Object.prototype.hasOwnProperty.call(value, "accountId")
107
+ );
108
+ }
109
+
100
110
  export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
101
111
  app.get("/v1/access/me", async (c) => {
102
112
  return c.json(await requireAccessContext(c, deps));
@@ -163,7 +173,18 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
163
173
  app.patch("/v1/workspaces/:workspaceId", async (c) => {
164
174
  const workspaceId = c.req.param("workspaceId");
165
175
  await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
166
- const payload = UpdateWorkspaceRequest.parse(await c.req.json());
176
+ const body = await c.req.json();
177
+ if (workspaceUpdateRequestsAccountTransfer(body)) {
178
+ throw new ApiHttpError(409, {
179
+ code: "conflict",
180
+ message:
181
+ "A workspace is permanently owned by one organization; use workspace grants for same-organization access handoff.",
182
+ retryable: false,
183
+ outcomeUnknown: false,
184
+ details: { code: "workspace_transfer_unsupported" },
185
+ });
186
+ }
187
+ const payload = UpdateWorkspaceRequest.parse(body);
167
188
  const workspace = await updateWorkspace(deps.db, workspaceId, {
168
189
  ...(payload.name !== undefined ? { name: payload.name.trim() } : {}),
169
190
  ...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
@@ -363,6 +384,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
363
384
  c.req.raw.signal,
364
385
  {
365
386
  observability: deps.observability,
387
+ actorEpoch: getManagedAuthRequestActorEpoch(c.req.raw) ?? undefined,
366
388
  reauthorize: async () => {
367
389
  await requireFreshAccessGrant(c, deps, workspaceId, "workspace:read");
368
390
  },
@@ -0,0 +1,121 @@
1
+ import type { WorkDiscoveryMatchClass } from "@opengeni/contracts";
2
+ import type { Observability } from "@opengeni/observability";
3
+
4
+ export type WorkDiscoverySurface = "first_party_mcp" | "agent_topology";
5
+ export type WorkDiscoveryMode = "browse" | "query" | "subject";
6
+ export type WorkDiscoveryOutcome = "ok" | "empty" | "disabled" | "error";
7
+ export type WorkDiscoveryAuthorizationScope = "workspace" | "scoped";
8
+
9
+ export type WorkDiscoveryObservation = {
10
+ surface: WorkDiscoverySurface;
11
+ mode: WorkDiscoveryMode;
12
+ outcome: WorkDiscoveryOutcome;
13
+ authorizationScope: WorkDiscoveryAuthorizationScope;
14
+ durationMs: number;
15
+ responseBytes: number;
16
+ resultCount: number;
17
+ overlapCount: number;
18
+ matchCounts: Partial<Record<WorkDiscoveryMatchClass, number>>;
19
+ };
20
+
21
+ type WorkDiscoveryObservedRow = {
22
+ relatedWork: {
23
+ match: { class: WorkDiscoveryMatchClass } | null;
24
+ possibleOverlap: boolean;
25
+ };
26
+ };
27
+
28
+ const RESULT_BUCKETS = [0, 1, 2, 4, 8, 16, 32, 64, 100];
29
+ const RESPONSE_BYTE_BUCKETS = [512, 1_024, 4_096, 16_384, 65_536, 128_000, 262_144];
30
+ const DURATION_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5];
31
+
32
+ export function summarizeWorkDiscoveryRows(
33
+ rows: readonly WorkDiscoveryObservedRow[],
34
+ ): Pick<WorkDiscoveryObservation, "resultCount" | "overlapCount" | "matchCounts"> {
35
+ const matchCounts: WorkDiscoveryObservation["matchCounts"] = {};
36
+ let overlapCount = 0;
37
+ for (const row of rows) {
38
+ if (row.relatedWork.possibleOverlap) overlapCount += 1;
39
+ const matchClass = row.relatedWork.match?.class;
40
+ if (matchClass) matchCounts[matchClass] = (matchCounts[matchClass] ?? 0) + 1;
41
+ }
42
+ return { resultCount: rows.length, overlapCount, matchCounts };
43
+ }
44
+
45
+ /**
46
+ * Low-cardinality work-discovery telemetry. Identifiers, search text, subject
47
+ * keys, titles, goals, claim labels, versions, and provenance are excluded by
48
+ * construction. An observability failure never changes product behavior.
49
+ */
50
+ export function observeWorkDiscovery(
51
+ observability: Observability | null | undefined,
52
+ observation: WorkDiscoveryObservation,
53
+ ): void {
54
+ if (!observability) return;
55
+ const labels = {
56
+ surface: observation.surface,
57
+ mode: observation.mode,
58
+ outcome: observation.outcome,
59
+ authorization_scope: observation.authorizationScope,
60
+ };
61
+ try {
62
+ observability.incrementCounter({
63
+ name: "opengeni_work_discovery_requests_total",
64
+ help: "Advisory work-discovery requests by bounded surface, mode, outcome, and authorization scope.",
65
+ labels,
66
+ });
67
+ observability.observeHistogram({
68
+ name: "opengeni_work_discovery_duration_seconds",
69
+ help: "Advisory work-discovery request duration in seconds.",
70
+ labels,
71
+ buckets: DURATION_BUCKETS,
72
+ value: Math.max(0, observation.durationMs) / 1_000,
73
+ });
74
+ observability.observeHistogram({
75
+ name: "opengeni_work_discovery_results",
76
+ help: "Bounded advisory work-discovery result rows per request.",
77
+ labels: { surface: observation.surface, mode: observation.mode },
78
+ buckets: RESULT_BUCKETS,
79
+ value: Math.max(0, Math.floor(observation.resultCount)),
80
+ });
81
+ observability.observeHistogram({
82
+ name: "opengeni_work_discovery_response_bytes",
83
+ help: "Serialized advisory work-discovery response bytes.",
84
+ labels: { surface: observation.surface, mode: observation.mode },
85
+ buckets: RESPONSE_BYTE_BUCKETS,
86
+ value: Math.max(0, Math.floor(observation.responseBytes)),
87
+ });
88
+ if (observation.overlapCount > 0) {
89
+ observability.incrementCounter({
90
+ name: "opengeni_work_discovery_overlap_results_total",
91
+ help: "Advisory discovery rows carrying a possible-overlap explanation.",
92
+ labels: { surface: observation.surface, mode: observation.mode },
93
+ amount: Math.floor(observation.overlapCount),
94
+ });
95
+ }
96
+ for (const matchClass of ["exact_subject", "title", "goal", "fuzzy"] as const) {
97
+ const amount = Math.floor(observation.matchCounts[matchClass] ?? 0);
98
+ if (amount < 1) continue;
99
+ observability.incrementCounter({
100
+ name: "opengeni_work_discovery_matches_total",
101
+ help: "Advisory discovery matches by stable explanation class.",
102
+ labels: {
103
+ surface: observation.surface,
104
+ mode: observation.mode,
105
+ match_class: matchClass,
106
+ },
107
+ amount,
108
+ });
109
+ }
110
+ } catch {
111
+ try {
112
+ observability.incrementCounter({
113
+ name: "opengeni_observability_observer_errors_total",
114
+ help: "Observability observer failures isolated from product execution.",
115
+ labels: { observer: "work_discovery" },
116
+ });
117
+ } catch {
118
+ // The metrics registry itself is unhealthy. Discovery remains authoritative.
119
+ }
120
+ }
121
+ }