@opengeni/api-router 0.5.0 → 0.5.2

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.
@@ -12,7 +12,7 @@ import { createObjectStorage } from "@opengeni/storage";
12
12
  import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
13
13
  import { Hono } from "hono";
14
14
  import { cors } from "hono/cors";
15
- import { HTTPException as HTTPException20 } from "hono/http-exception";
15
+ import { HTTPException as HTTPException22 } from "hono/http-exception";
16
16
  import { hasPermission as hasPermission4, requireAccessGrant as requireAccessGrant16, requirePermission } from "@opengeni/core";
17
17
 
18
18
  // src/auth/managed-auth.ts
@@ -1836,6 +1836,9 @@ function isAuthExempt(c, settings) {
1836
1836
  if (path === "/v1/integrations/oauth/callback" || path === "/v1/integrations/oauth/client-metadata.json") {
1837
1837
  return true;
1838
1838
  }
1839
+ if (path.startsWith("/v1/catalog-assets/")) {
1840
+ return true;
1841
+ }
1839
1842
  if (githubConnectPathPattern.test(path)) {
1840
1843
  return true;
1841
1844
  }
@@ -1964,6 +1967,91 @@ function registerCapabilityRoutes(app, deps) {
1964
1967
  });
1965
1968
  }
1966
1969
 
1970
+ // src/routes/catalog-assets.ts
1971
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
1972
+ var CATALOG_ASSET_PREFIX = "catalog-assets/";
1973
+ var MAX_KEY_LENGTH = 512;
1974
+ var PRINTABLE_ASCII = /^[\x20-\x7e]+$/;
1975
+ var CONTENT_TYPE_BY_EXTENSION = {
1976
+ png: "image/png",
1977
+ jpg: "image/jpeg",
1978
+ jpeg: "image/jpeg",
1979
+ svg: "image/svg+xml",
1980
+ webp: "image/webp",
1981
+ gif: "image/gif",
1982
+ ico: "image/x-icon"
1983
+ };
1984
+ function registerCatalogAssetRoutes(app, deps) {
1985
+ const { settings, objectStorage } = deps;
1986
+ app.get("/v1/catalog-assets/*", async (c) => {
1987
+ if (!settings.integrationsEnabled) {
1988
+ throw new HTTPException3(404, { message: "integrations are not enabled for this deployment" });
1989
+ }
1990
+ if (!objectStorage) {
1991
+ throw new HTTPException3(404, { message: "asset not found" });
1992
+ }
1993
+ const key = catalogAssetKeyFromPath(new URL(c.req.url).pathname);
1994
+ if (!key) {
1995
+ throw new HTTPException3(404, { message: "asset not found" });
1996
+ }
1997
+ const contentType = contentTypeForKey(key);
1998
+ if (!contentType) {
1999
+ throw new HTTPException3(404, { message: "asset not found" });
2000
+ }
2001
+ const object3 = await objectStorage.getObjectBytes(key);
2002
+ if (!object3) {
2003
+ throw new HTTPException3(404, { message: "asset not found" });
2004
+ }
2005
+ const etag = etagForKey(key);
2006
+ const headers = {
2007
+ "Cache-Control": "public, max-age=31536000, immutable",
2008
+ ETag: etag,
2009
+ "X-Content-Type-Options": "nosniff",
2010
+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
2011
+ };
2012
+ if (ifNoneMatchSatisfied(c.req.header("if-none-match"), etag)) {
2013
+ return c.body(null, 304, headers);
2014
+ }
2015
+ return c.body(new Uint8Array(object3.bytes), 200, { ...headers, "Content-Type": contentType });
2016
+ });
2017
+ }
2018
+ function catalogAssetKeyFromPath(pathname) {
2019
+ const prefix = "/v1/";
2020
+ if (!pathname.startsWith(prefix)) {
2021
+ return null;
2022
+ }
2023
+ let key;
2024
+ try {
2025
+ key = decodeURIComponent(pathname.slice(prefix.length));
2026
+ } catch {
2027
+ return null;
2028
+ }
2029
+ if (key.length === 0 || key.length > MAX_KEY_LENGTH || !key.startsWith(CATALOG_ASSET_PREFIX) || key.includes("..") || key.includes("\\") || key.includes("//") || !PRINTABLE_ASCII.test(key)) {
2030
+ return null;
2031
+ }
2032
+ return key;
2033
+ }
2034
+ function contentTypeForKey(key) {
2035
+ const match = /\.([a-zA-Z0-9]+)$/.exec(key);
2036
+ const ext = match?.[1]?.toLowerCase();
2037
+ return ext ? CONTENT_TYPE_BY_EXTENSION[ext] ?? null : null;
2038
+ }
2039
+ function etagForKey(key) {
2040
+ const filename = key.slice(key.lastIndexOf("/") + 1);
2041
+ const dot = filename.lastIndexOf(".");
2042
+ const digest = dot === -1 ? filename : filename.slice(0, dot);
2043
+ return `"${digest}"`;
2044
+ }
2045
+ function ifNoneMatchSatisfied(header, etag) {
2046
+ if (!header) {
2047
+ return false;
2048
+ }
2049
+ if (header.trim() === "*") {
2050
+ return true;
2051
+ }
2052
+ return header.split(",").map((value) => value.trim()).includes(etag);
2053
+ }
2054
+
1967
2055
  // src/routes/codex.ts
1968
2056
  import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
1969
2057
  import {
@@ -1999,7 +2087,7 @@ import {
1999
2087
  CODEX_ROTATION_STRATEGIES
2000
2088
  } from "@opengeni/db";
2001
2089
  import { createSignedState as createSignedState2, readSignedState } from "@opengeni/github";
2002
- import { HTTPException as HTTPException3 } from "hono/http-exception";
2090
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
2003
2091
  import { requireAccessGrant as requireAccessGrant2 } from "@opengeni/core";
2004
2092
  var CODEX_PROVIDER_LABEL = "Codex subscription \xB7 no credits";
2005
2093
  function codexAccountJson(row) {
@@ -2043,7 +2131,7 @@ function registerCodexRoutes(app, deps) {
2043
2131
  try {
2044
2132
  start = await startDeviceCode();
2045
2133
  } catch (error) {
2046
- throw new HTTPException3(502, { message: error instanceof CodexDeviceError ? error.message : "failed to start Codex device login" });
2134
+ throw new HTTPException4(502, { message: error instanceof CodexDeviceError ? error.message : "failed to start Codex device login" });
2047
2135
  }
2048
2136
  const state = createSignedState2(githubStateSecret, { workspaceId, deviceAuthId: start.deviceAuthId, userCode: start.userCode });
2049
2137
  return c.json({ userCode: start.userCode, verificationUri: start.verificationUri, intervalSeconds: start.intervalSeconds, state });
@@ -2054,7 +2142,7 @@ function registerCodexRoutes(app, deps) {
2054
2142
  const { state } = await c.req.json();
2055
2143
  const payload = state ? readSignedState(state, githubStateSecret) : null;
2056
2144
  if (!payload || payload.workspaceId !== workspaceId || !payload.deviceAuthId || !payload.userCode) {
2057
- throw new HTTPException3(400, { message: "codex connect state is invalid or expired" });
2145
+ throw new HTTPException4(400, { message: "codex connect state is invalid or expired" });
2058
2146
  }
2059
2147
  if (typeof payload.iat === "number" && Date.now() / 1e3 - payload.iat > CODEX_DEVICE_EXPIRY_SECONDS) {
2060
2148
  return c.json({ status: "expired" });
@@ -2063,7 +2151,7 @@ function registerCodexRoutes(app, deps) {
2063
2151
  try {
2064
2152
  poll = await pollDeviceCode({ deviceAuthId: payload.deviceAuthId, userCode: payload.userCode });
2065
2153
  } catch (error) {
2066
- throw new HTTPException3(502, { message: error instanceof CodexDeviceError ? error.message : "codex device poll failed" });
2154
+ throw new HTTPException4(502, { message: error instanceof CodexDeviceError ? error.message : "codex device poll failed" });
2067
2155
  }
2068
2156
  if (poll.status === "pending") {
2069
2157
  return c.json({ status: "pending" });
@@ -2075,12 +2163,12 @@ function registerCodexRoutes(app, deps) {
2075
2163
  try {
2076
2164
  tokens = await exchangeDeviceCode({ authorizationCode: poll.authorizationCode, codeVerifier: poll.codeVerifier });
2077
2165
  } catch (error) {
2078
- throw new HTTPException3(502, { message: error instanceof CodexDeviceError ? error.message : "codex token exchange failed" });
2166
+ throw new HTTPException4(502, { message: error instanceof CodexDeviceError ? error.message : "codex token exchange failed" });
2079
2167
  }
2080
2168
  const id = parseIdToken(tokens.idToken);
2081
2169
  const key = environmentsEncryptionKeyBytes2(settings);
2082
2170
  if (!key) {
2083
- throw new HTTPException3(500, { message: "OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured" });
2171
+ throw new HTTPException4(500, { message: "OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured" });
2084
2172
  }
2085
2173
  const upserted = await upsertCodexSubscriptionCredential(db, {
2086
2174
  accountId: grant.accountId,
@@ -2174,7 +2262,7 @@ function registerCodexRoutes(app, deps) {
2174
2262
  const accountId = c.req.param("accountId");
2175
2263
  const activated = await setActiveCodexCredential(db, workspaceId, accountId);
2176
2264
  if (!activated) {
2177
- throw new HTTPException3(404, { message: "codex account not found" });
2265
+ throw new HTTPException4(404, { message: "codex account not found" });
2178
2266
  }
2179
2267
  return c.json({ activated: true, accountId });
2180
2268
  });
@@ -2188,17 +2276,17 @@ function registerCodexRoutes(app, deps) {
2188
2276
  }
2189
2277
  if (typeof body.rotationStrategy === "string") {
2190
2278
  if (!CODEX_ROTATION_STRATEGIES.includes(body.rotationStrategy)) {
2191
- throw new HTTPException3(400, { message: "invalid rotation strategy" });
2279
+ throw new HTTPException4(400, { message: "invalid rotation strategy" });
2192
2280
  }
2193
2281
  patch.rotationStrategy = body.rotationStrategy;
2194
2282
  }
2195
2283
  if (patch.rotationEnabled === void 0 && patch.rotationStrategy === void 0) {
2196
- throw new HTTPException3(400, { message: "no settings to update" });
2284
+ throw new HTTPException4(400, { message: "no settings to update" });
2197
2285
  }
2198
2286
  await ensureCodexRotationSettings(db, grant.accountId, workspaceId);
2199
2287
  const updated = await updateCodexRotationSettings(db, workspaceId, patch);
2200
2288
  if (!updated) {
2201
- throw new HTTPException3(404, { message: "codex rotation settings not found" });
2289
+ throw new HTTPException4(404, { message: "codex rotation settings not found" });
2202
2290
  }
2203
2291
  return c.json({
2204
2292
  rotationEnabled: updated.rotationEnabled,
@@ -2214,12 +2302,12 @@ function registerCodexRoutes(app, deps) {
2214
2302
  const label = typeof body.label === "string" ? body.label : null;
2215
2303
  const renamed = await renameCodexAccount(db, workspaceId, accountId, label);
2216
2304
  if (!renamed) {
2217
- throw new HTTPException3(404, { message: "codex account not found" });
2305
+ throw new HTTPException4(404, { message: "codex account not found" });
2218
2306
  }
2219
2307
  const accounts = await listCodexAccountStatuses(db, workspaceId);
2220
2308
  const row = accounts.find((account) => account.id === accountId);
2221
2309
  if (!row) {
2222
- throw new HTTPException3(404, { message: "codex account not found" });
2310
+ throw new HTTPException4(404, { message: "codex account not found" });
2223
2311
  }
2224
2312
  return c.json(codexAccountJson(row));
2225
2313
  });
@@ -2241,7 +2329,7 @@ function registerCodexRoutes(app, deps) {
2241
2329
  await requireAccessGrant2(c, deps, workspaceId, "workspace:read");
2242
2330
  const status = await getCodexCredentialStatus(db, workspaceId);
2243
2331
  if (!status?.credentialId) {
2244
- throw new HTTPException3(404, { message: "codex subscription is not connected" });
2332
+ throw new HTTPException4(404, { message: "codex subscription is not connected" });
2245
2333
  }
2246
2334
  const payload = await fetchCodexUsageForAccount(db, settings, workspaceId, status.credentialId);
2247
2335
  return c.json(codexUsageJson(payload));
@@ -2252,7 +2340,7 @@ function registerCodexRoutes(app, deps) {
2252
2340
  const accountId = c.req.param("accountId");
2253
2341
  const accounts = await listCodexAccountStatuses(db, workspaceId);
2254
2342
  if (!accounts.some((account) => account.id === accountId)) {
2255
- throw new HTTPException3(404, { message: "codex account not found" });
2343
+ throw new HTTPException4(404, { message: "codex account not found" });
2256
2344
  }
2257
2345
  const payload = await fetchCodexUsageForAccount(db, settings, workspaceId, accountId);
2258
2346
  return c.json(codexUsageJson(payload));
@@ -2297,7 +2385,7 @@ import {
2297
2385
  revokeConnection,
2298
2386
  updateConnection as updateConnection2
2299
2387
  } from "@opengeni/db";
2300
- import { HTTPException as HTTPException5 } from "hono/http-exception";
2388
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
2301
2389
 
2302
2390
  // src/integrations/oauth-client.ts
2303
2391
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
@@ -2321,7 +2409,19 @@ import { Buffer } from "buffer";
2321
2409
  import { createHash, randomBytes } from "crypto";
2322
2410
  import { lookup } from "dns/promises";
2323
2411
  import { isIP } from "net";
2324
- import { HTTPException as HTTPException4 } from "hono/http-exception";
2412
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
2413
+
2414
+ // src/integrations/provider-domain.ts
2415
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
2416
+ function canonicalProviderDomain(value) {
2417
+ const canonical = value.trim().toLowerCase().replace(/^www\./, "");
2418
+ if (!canonical) {
2419
+ throw new HTTPException5(400, { message: "providerDomain must not be empty" });
2420
+ }
2421
+ return canonical;
2422
+ }
2423
+
2424
+ // src/integrations/oauth-client.ts
2325
2425
  var oauthStateTtlMs = 10 * 60 * 1e3;
2326
2426
  async function startMcpOAuth(deps, context) {
2327
2427
  const { db, settings } = deps;
@@ -2333,7 +2433,7 @@ async function startMcpOAuth(deps, context) {
2333
2433
  const metadataUrl = `${baseUrl}/v1/integrations/oauth/client-metadata.json`;
2334
2434
  const existing = context.payload.connectionId ? await getConnectionMetadata(db, context.workspaceId, context.payload.connectionId, context.subjectId) : null;
2335
2435
  if (context.payload.connectionId && !existing) {
2336
- throw new HTTPException4(404, { message: "connection not found" });
2436
+ throw new HTTPException6(404, { message: "connection not found" });
2337
2437
  }
2338
2438
  const discovery = await discoverMcpOAuth(resource, settings);
2339
2439
  const client = await registerOAuthClient(db, settings, discovery.as, metadataUrl, redirectUri);
@@ -2376,7 +2476,7 @@ async function startMcpOAuth(deps, context) {
2376
2476
  async function completeMcpOAuthCallback(deps, input) {
2377
2477
  const { db, settings } = deps;
2378
2478
  if (!input.state) {
2379
- throw new HTTPException4(400, { message: "missing OAuth state" });
2479
+ throw new HTTPException6(400, { message: "missing OAuth state" });
2380
2480
  }
2381
2481
  const state = readOAuthState(input.state, settings);
2382
2482
  if (!input.code) {
@@ -2391,7 +2491,7 @@ async function completeMcpOAuthCallback(deps, input) {
2391
2491
  now: /* @__PURE__ */ new Date()
2392
2492
  });
2393
2493
  if (!consumed) {
2394
- throw new HTTPException4(400, { message: "OAuth state has already been used" });
2494
+ throw new HTTPException6(400, { message: "OAuth state has already been used" });
2395
2495
  }
2396
2496
  try {
2397
2497
  const baseUrl = integrationBaseUrl(settings.publicBaseUrl, input.requestUrl);
@@ -2446,11 +2546,11 @@ async function completeMcpOAuthCallback(deps, input) {
2446
2546
  createdBySubjectId: state.subjectId
2447
2547
  });
2448
2548
  if (!connection) {
2449
- throw new HTTPException4(409, { message: "connection changed during OAuth reconnect; start again" });
2549
+ throw new HTTPException6(409, { message: "connection changed during OAuth reconnect; start again" });
2450
2550
  }
2451
- return { redirectTo: callbackReturnPath(state.returnPath, "success", { connectionId: connection.id }) };
2551
+ return { redirectTo: callbackReturnPath(state.returnPath, "success", { connectionId: connection.id, providerDomain: connection.providerDomain }) };
2452
2552
  } catch (error) {
2453
- if (error instanceof HTTPException4 && error.status >= 400 && error.status < 500) {
2553
+ if (error instanceof HTTPException6 && error.status >= 400 && error.status < 500) {
2454
2554
  throw error;
2455
2555
  }
2456
2556
  return { redirectTo: callbackReturnPath(state.returnPath, "error", { reason: "oauth_callback_failed" }) };
@@ -2462,7 +2562,7 @@ function integrationBaseUrl(publicBaseUrl, requestUrl) {
2462
2562
  function requireIntegrationsStateSecret(settings) {
2463
2563
  const secret = settings.integrationsStateSecret?.trim();
2464
2564
  if (!secret) {
2465
- throw new HTTPException4(503, { message: "integrations OAuth requires OPENGENI_INTEGRATIONS_STATE_SECRET" });
2565
+ throw new HTTPException6(503, { message: "integrations OAuth requires OPENGENI_INTEGRATIONS_STATE_SECRET" });
2466
2566
  }
2467
2567
  return secret;
2468
2568
  }
@@ -2471,11 +2571,11 @@ async function discoverMcpOAuth(resource, settings) {
2471
2571
  const prm = await discoverProtectedResourceMetadata(resource, settings, challenge.resourceMetadata);
2472
2572
  const authorizationServer = prm.authorizationServers[0];
2473
2573
  if (!authorizationServer) {
2474
- throw new HTTPException4(422, { message: "MCP protected resource metadata did not advertise an authorization server" });
2574
+ throw new HTTPException6(422, { message: "MCP protected resource metadata did not advertise an authorization server" });
2475
2575
  }
2476
2576
  const as = await discoverAuthorizationServerMetadata(authorizationServer, settings);
2477
2577
  if (!as.codeChallengeMethodsSupported.includes("S256")) {
2478
- throw new HTTPException4(422, { message: "authorization server does not support required PKCE S256" });
2578
+ throw new HTTPException6(422, { message: "authorization server does not support required PKCE S256" });
2479
2579
  }
2480
2580
  return { challenge, prm, as };
2481
2581
  }
@@ -2496,7 +2596,7 @@ async function discoverProtectedResourceMetadata(resource, settings, advertisedU
2496
2596
  ]);
2497
2597
  for (const candidate of candidates) {
2498
2598
  const payload = await fetchJsonObject(candidate, settings).catch((error) => {
2499
- if (error instanceof HTTPException4) {
2599
+ if (error instanceof HTTPException6) {
2500
2600
  throw error;
2501
2601
  }
2502
2602
  return null;
@@ -2515,7 +2615,7 @@ async function discoverProtectedResourceMetadata(resource, settings, advertisedU
2515
2615
  ...stringValue(payload.resource) ? { resource: stringValue(payload.resource) } : {}
2516
2616
  };
2517
2617
  }
2518
- throw new HTTPException4(422, { message: "could not discover MCP protected resource metadata" });
2618
+ throw new HTTPException6(422, { message: "could not discover MCP protected resource metadata" });
2519
2619
  }
2520
2620
  async function discoverAuthorizationServerMetadata(authorizationServer, settings) {
2521
2621
  const candidates = uniqueStrings([
@@ -2525,7 +2625,7 @@ async function discoverAuthorizationServerMetadata(authorizationServer, settings
2525
2625
  ]);
2526
2626
  for (const candidate of candidates) {
2527
2627
  const payload = await fetchJsonObject(candidate, settings).catch((error) => {
2528
- if (error instanceof HTTPException4) {
2628
+ if (error instanceof HTTPException6) {
2529
2629
  throw error;
2530
2630
  }
2531
2631
  return null;
@@ -2549,7 +2649,7 @@ async function discoverAuthorizationServerMetadata(authorizationServer, settings
2549
2649
  ...stringValue(payload.registration_endpoint) ? { registrationEndpoint: stringValue(payload.registration_endpoint) } : {}
2550
2650
  };
2551
2651
  }
2552
- throw new HTTPException4(422, { message: "could not discover OAuth authorization server metadata" });
2652
+ throw new HTTPException6(422, { message: "could not discover OAuth authorization server metadata" });
2553
2653
  }
2554
2654
  async function registerOAuthClient(db, settings, as, metadataUrl, redirectUri) {
2555
2655
  const operator = operatorClientForAs(settings, as);
@@ -2577,7 +2677,7 @@ async function registerOAuthClient(db, settings, as, metadataUrl, redirectUri) {
2577
2677
  };
2578
2678
  }
2579
2679
  if (!as.registrationEndpoint) {
2580
- throw new HTTPException4(422, {
2680
+ throw new HTTPException6(422, {
2581
2681
  message: "manual OAuth client credentials are required for this authorization server"
2582
2682
  });
2583
2683
  }
@@ -2597,7 +2697,7 @@ async function registerOAuthClient(db, settings, as, metadataUrl, redirectUri) {
2597
2697
  if (storedWinner.clientId !== dcr.clientId) {
2598
2698
  const winner = await loadIntegrationOAuthClient(db, settings, as.issuer);
2599
2699
  if (!winner) {
2600
- throw new HTTPException4(422, { message: "OAuth client registration could not be loaded after a registration race" });
2700
+ throw new HTTPException6(422, { message: "OAuth client registration could not be loaded after a registration race" });
2601
2701
  }
2602
2702
  return dcrRegistrationFromStored(winner);
2603
2703
  }
@@ -2649,7 +2749,7 @@ function normalizedIssuerKey(value) {
2649
2749
  }
2650
2750
  async function dynamicClientRegistration(settings, as, redirectUri) {
2651
2751
  if (!as.registrationEndpoint) {
2652
- throw new HTTPException4(422, { message: "authorization server does not support dynamic client registration" });
2752
+ throw new HTTPException6(422, { message: "authorization server does not support dynamic client registration" });
2653
2753
  }
2654
2754
  await assertOAuthFetchAllowed(as.registrationEndpoint, settings);
2655
2755
  const response = await fetchOAuth(as.registrationEndpoint, settings, {
@@ -2664,12 +2764,12 @@ async function dynamicClientRegistration(settings, as, redirectUri) {
2664
2764
  })
2665
2765
  });
2666
2766
  if (!response.ok) {
2667
- throw new HTTPException4(422, { message: `dynamic client registration failed with HTTP ${response.status}` });
2767
+ throw new HTTPException6(422, { message: `dynamic client registration failed with HTTP ${response.status}` });
2668
2768
  }
2669
2769
  const payload = await response.json();
2670
2770
  const clientId = stringValue(payload.client_id);
2671
2771
  if (!clientId) {
2672
- throw new HTTPException4(422, { message: "dynamic client registration response did not include client_id" });
2772
+ throw new HTTPException6(422, { message: "dynamic client registration response did not include client_id" });
2673
2773
  }
2674
2774
  const clientSecret = stringValue(payload.client_secret);
2675
2775
  return {
@@ -2698,12 +2798,12 @@ function buildAuthorizationUrl(input) {
2698
2798
  function readOAuthState(state, settings) {
2699
2799
  const payload = readSignedState2(state, requireIntegrationsStateSecret(settings));
2700
2800
  if (!payload) {
2701
- throw new HTTPException4(400, { message: "invalid or expired OAuth state" });
2801
+ throw new HTTPException6(400, { message: "invalid or expired OAuth state" });
2702
2802
  }
2703
2803
  const nowSeconds = Math.floor(Date.now() / 1e3);
2704
2804
  const iat = numberValue(payload.iat);
2705
2805
  if (iat === void 0 || nowSeconds - iat > oauthStateTtlMs / 1e3 || nowSeconds < iat) {
2706
- throw new HTTPException4(400, { message: "invalid or expired OAuth state" });
2806
+ throw new HTTPException6(400, { message: "invalid or expired OAuth state" });
2707
2807
  }
2708
2808
  const parsed = {
2709
2809
  accountId: requiredString(payload.accountId, "state.accountId"),
@@ -2745,7 +2845,7 @@ async function clientForState(db, settings, state) {
2745
2845
  if (state.clientRegistrationMethod === "dcr") {
2746
2846
  const stored = await loadIntegrationOAuthClient(db, settings, state.issuer);
2747
2847
  if (!stored || stored.clientId !== state.clientId) {
2748
- throw new HTTPException4(400, { message: "OAuth client registration is no longer available" });
2848
+ throw new HTTPException6(400, { message: "OAuth client registration is no longer available" });
2749
2849
  }
2750
2850
  return {
2751
2851
  method: "dcr",
@@ -2758,7 +2858,7 @@ async function clientForState(db, settings, state) {
2758
2858
  }
2759
2859
  const entry = operatorClientEntryFor(settings, [state.issuer, state.authorizationServer]);
2760
2860
  if (!entry || entry.clientId !== state.clientId) {
2761
- throw new HTTPException4(400, { message: "operator OAuth client credentials are no longer available" });
2861
+ throw new HTTPException6(400, { message: "operator OAuth client credentials are no longer available" });
2762
2862
  }
2763
2863
  return {
2764
2864
  method: "operator",
@@ -2845,27 +2945,24 @@ function callbackReturnPath(returnPath, status, params) {
2845
2945
  }
2846
2946
  function canonicalMcpResource(value) {
2847
2947
  if (!value) {
2848
- throw new HTTPException4(400, { message: "mcpUrl is required" });
2948
+ throw new HTTPException6(400, { message: "mcpUrl is required" });
2849
2949
  }
2850
2950
  let url;
2851
2951
  try {
2852
2952
  url = new URL(value);
2853
2953
  } catch {
2854
- throw new HTTPException4(422, { message: "MCP resource URL is invalid" });
2954
+ throw new HTTPException6(422, { message: "MCP resource URL is invalid" });
2855
2955
  }
2856
2956
  url.hash = "";
2857
2957
  return url.toString();
2858
2958
  }
2859
- function canonicalProviderDomain(value) {
2860
- return value.trim().toLowerCase().replace(/^www\./, "");
2861
- }
2862
2959
  function safeReturnPath(value) {
2863
2960
  if (!value.startsWith("/") || value.startsWith("//")) {
2864
- throw new HTTPException4(400, { message: "OAuth returnPath must be a relative path" });
2961
+ throw new HTTPException6(400, { message: "OAuth returnPath must be a relative path" });
2865
2962
  }
2866
2963
  const parsed = new URL(value, "https://opengeni.local");
2867
2964
  if (parsed.origin !== "https://opengeni.local") {
2868
- throw new HTTPException4(400, { message: "OAuth returnPath must be a relative path" });
2965
+ throw new HTTPException6(400, { message: "OAuth returnPath must be a relative path" });
2869
2966
  }
2870
2967
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
2871
2968
  }
@@ -2887,39 +2984,39 @@ async function fetchOAuth(rawUrl, settings, init = {}, hop = 0) {
2887
2984
  return response;
2888
2985
  }
2889
2986
  if (hop >= 3) {
2890
- throw new HTTPException4(422, { message: "OAuth fetch exceeded maximum redirect hops" });
2987
+ throw new HTTPException6(422, { message: "OAuth fetch exceeded maximum redirect hops" });
2891
2988
  }
2892
2989
  const location = response.headers.get("location");
2893
2990
  if (!location) {
2894
- throw new HTTPException4(422, { message: "OAuth fetch redirect was missing Location" });
2991
+ throw new HTTPException6(422, { message: "OAuth fetch redirect was missing Location" });
2895
2992
  }
2896
2993
  let nextUrl;
2897
2994
  try {
2898
2995
  nextUrl = new URL(location, rawUrl).toString();
2899
2996
  } catch {
2900
- throw new HTTPException4(422, { message: "OAuth fetch redirect Location was invalid" });
2997
+ throw new HTTPException6(422, { message: "OAuth fetch redirect Location was invalid" });
2901
2998
  }
2902
2999
  return await fetchOAuth(nextUrl, settings, init, hop + 1);
2903
3000
  }
2904
3001
  async function assertOAuthFetchAllowed(rawUrl, settings) {
2905
3002
  const url = new URL(rawUrl);
2906
3003
  if (!["https:", "http:"].includes(url.protocol)) {
2907
- throw new HTTPException4(422, { message: "OAuth discovery only supports http and https URLs" });
3004
+ throw new HTTPException6(422, { message: "OAuth discovery only supports http and https URLs" });
2908
3005
  }
2909
3006
  if (settings.integrationsAllowPrivateNetworkTargets || ["local", "test"].includes(settings.environment)) {
2910
3007
  return;
2911
3008
  }
2912
3009
  if (url.protocol !== "https:") {
2913
- throw new HTTPException4(422, { message: "OAuth discovery targets must use https outside local/test" });
3010
+ throw new HTTPException6(422, { message: "OAuth discovery targets must use https outside local/test" });
2914
3011
  }
2915
3012
  const hostname = url.hostname.toLowerCase();
2916
3013
  if (hostname === "localhost" || hostname.endsWith(".localhost")) {
2917
- throw new HTTPException4(422, { message: "OAuth discovery may not target localhost" });
3014
+ throw new HTTPException6(422, { message: "OAuth discovery may not target localhost" });
2918
3015
  }
2919
3016
  const literal2 = isIP(hostname);
2920
3017
  const addresses = literal2 ? [hostname] : (await lookup(hostname, { all: true })).map((entry) => entry.address);
2921
3018
  if (addresses.some(isPrivateAddress)) {
2922
- throw new HTTPException4(422, { message: "OAuth discovery may not target private network addresses" });
3019
+ throw new HTTPException6(422, { message: "OAuth discovery may not target private network addresses" });
2923
3020
  }
2924
3021
  }
2925
3022
  function parseWwwAuthenticate(header) {
@@ -2978,7 +3075,7 @@ function registrationMethod(value) {
2978
3075
  if (value === "operator" || value === "cimd" || value === "dcr") {
2979
3076
  return value;
2980
3077
  }
2981
- throw new HTTPException4(400, { message: "invalid OAuth state" });
3078
+ throw new HTTPException6(400, { message: "invalid OAuth state" });
2982
3079
  }
2983
3080
  function expiresAtFromTokenResponse(payload) {
2984
3081
  const expiresAt = stringValue(payload.expires_at);
@@ -3013,7 +3110,7 @@ function numberValue(value) {
3013
3110
  function requiredString(value, field) {
3014
3111
  const result = stringValue(value);
3015
3112
  if (!result) {
3016
- throw new HTTPException4(400, { message: `invalid OAuth state: missing ${field}` });
3113
+ throw new HTTPException6(400, { message: `invalid OAuth state: missing ${field}` });
3017
3114
  }
3018
3115
  return result;
3019
3116
  }
@@ -3023,7 +3120,7 @@ function registerConnectionRoutes(app, deps) {
3023
3120
  const { db, settings } = deps;
3024
3121
  function assertIntegrationsEnabled() {
3025
3122
  if (!settings.integrationsEnabled) {
3026
- throw new HTTPException5(404, { message: "integrations are not enabled for this deployment" });
3123
+ throw new HTTPException7(404, { message: "integrations are not enabled for this deployment" });
3027
3124
  }
3028
3125
  }
3029
3126
  app.get("/v1/workspaces/:workspaceId/connections", async (c) => {
@@ -3043,7 +3140,7 @@ function registerConnectionRoutes(app, deps) {
3043
3140
  accountId: grant.accountId,
3044
3141
  workspaceId,
3045
3142
  subjectId,
3046
- providerDomain: payload.providerDomain,
3143
+ providerDomain: canonicalProviderDomain(payload.providerDomain),
3047
3144
  kind: payload.kind,
3048
3145
  credentialEncrypted: encryptCredentialBundle(key, payload.credential),
3049
3146
  grantedScopes: payload.grantedScopes,
@@ -3058,7 +3155,7 @@ function registerConnectionRoutes(app, deps) {
3058
3155
  const grant = await requireAccessGrant3(c, deps, workspaceId, "connections:read");
3059
3156
  const connection = await getConnectionMetadata2(db, workspaceId, c.req.param("connectionId"), grant.subjectId);
3060
3157
  if (!connection) {
3061
- throw new HTTPException5(404, { message: "connection not found" });
3158
+ throw new HTTPException7(404, { message: "connection not found" });
3062
3159
  }
3063
3160
  return c.json(ConnectionResponse.parse({ connection }));
3064
3161
  });
@@ -3068,10 +3165,10 @@ function registerConnectionRoutes(app, deps) {
3068
3165
  const payload = UpdateConnectionRequest.parse(await c.req.json());
3069
3166
  if (payload.status !== void 0) {
3070
3167
  if (payload.status !== "active") {
3071
- throw new HTTPException5(400, { message: 'status can only be set to "active"; use DELETE to revoke' });
3168
+ throw new HTTPException7(400, { message: 'status can only be set to "active"; use DELETE to revoke' });
3072
3169
  }
3073
3170
  if (payload.credential === void 0) {
3074
- throw new HTTPException5(400, { message: "reactivating a connection requires a new credential" });
3171
+ throw new HTTPException7(400, { message: "reactivating a connection requires a new credential" });
3075
3172
  }
3076
3173
  }
3077
3174
  const key = payload.credential === void 0 ? null : requireEnvironmentEncryption3(settings);
@@ -3081,7 +3178,7 @@ function registerConnectionRoutes(app, deps) {
3081
3178
  connectionId: c.req.param("connectionId"),
3082
3179
  visibleToSubjectId: grant.subjectId,
3083
3180
  updatedBySubjectId: grant.subjectId,
3084
- ...payload.providerDomain !== void 0 ? { providerDomain: payload.providerDomain } : {},
3181
+ ...payload.providerDomain !== void 0 ? { providerDomain: canonicalProviderDomain(payload.providerDomain) } : {},
3085
3182
  ...subjectId !== void 0 ? { subjectId } : {},
3086
3183
  ...payload.kind !== void 0 ? { kind: payload.kind } : {},
3087
3184
  ...payload.status !== void 0 ? { status: payload.status } : {},
@@ -3091,7 +3188,7 @@ function registerConnectionRoutes(app, deps) {
3091
3188
  ...payload.metadata !== void 0 ? { metadata: payload.metadata } : {}
3092
3189
  });
3093
3190
  if (!connection) {
3094
- throw new HTTPException5(404, { message: "connection not found" });
3191
+ throw new HTTPException7(404, { message: "connection not found" });
3095
3192
  }
3096
3193
  return c.json(ConnectionResponse.parse({ connection }));
3097
3194
  });
@@ -3100,7 +3197,7 @@ function registerConnectionRoutes(app, deps) {
3100
3197
  const grant = await requireAccessGrant3(c, deps, workspaceId, "connections:write");
3101
3198
  const connection = await revokeConnection(db, workspaceId, c.req.param("connectionId"), grant.subjectId);
3102
3199
  if (!connection) {
3103
- throw new HTTPException5(404, { message: "connection not found" });
3200
+ throw new HTTPException7(404, { message: "connection not found" });
3104
3201
  }
3105
3202
  return c.json(ConnectionResponse.parse({ connection }));
3106
3203
  });
@@ -3110,7 +3207,7 @@ function registerConnectionRoutes(app, deps) {
3110
3207
  const grant = await requireAccessGrant3(c, deps, workspaceId, "connections:write");
3111
3208
  const parsed = OAuthStartRequest.safeParse(await c.req.json());
3112
3209
  if (!parsed.success) {
3113
- throw new HTTPException5(400, { message: parsed.error.issues[0]?.message ?? "invalid OAuth start request" });
3210
+ throw new HTTPException7(400, { message: parsed.error.issues[0]?.message ?? "invalid OAuth start request" });
3114
3211
  }
3115
3212
  const payload = parsed.data;
3116
3213
  const result = await startMcpOAuth({ db, settings }, {
@@ -3149,7 +3246,7 @@ function writableSubjectId(requested, grantSubjectId) {
3149
3246
  return null;
3150
3247
  }
3151
3248
  if (requested !== grantSubjectId) {
3152
- throw new HTTPException5(403, { message: "cannot write a connection for another subject" });
3249
+ throw new HTTPException7(403, { message: "cannot write a connection for another subject" });
3153
3250
  }
3154
3251
  return requested;
3155
3252
  }
@@ -3187,7 +3284,7 @@ import {
3187
3284
  searchDocuments as searchDocuments2
3188
3285
  } from "@opengeni/documents";
3189
3286
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
3190
- import { HTTPException as HTTPException6 } from "hono/http-exception";
3287
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
3191
3288
  import { requireAccessGrant as requireAccessGrant4 } from "@opengeni/core";
3192
3289
  import { recordWorkspaceUsage as recordWorkspaceUsage2, requireLimit as requireLimit2 } from "@opengeni/core";
3193
3290
 
@@ -3341,7 +3438,7 @@ function registerDocumentRoutes(app, deps) {
3341
3438
  await requireAccessGrant4(c, deps, workspaceId, "documents:search");
3342
3439
  const base = await getDocumentBase(db, workspaceId, c.req.param("baseId"));
3343
3440
  if (!base) {
3344
- throw new HTTPException6(404, { message: "document base not found" });
3441
+ throw new HTTPException8(404, { message: "document base not found" });
3345
3442
  }
3346
3443
  return c.json(DocumentBase.parse(base));
3347
3444
  });
@@ -3349,7 +3446,7 @@ function registerDocumentRoutes(app, deps) {
3349
3446
  const workspaceId = c.req.param("workspaceId");
3350
3447
  const grant = await requireAccessGrant4(c, deps, workspaceId, "documents:manage");
3351
3448
  if (!objectStorage) {
3352
- throw new HTTPException6(503, { message: "object storage is not configured" });
3449
+ throw new HTTPException8(503, { message: "object storage is not configured" });
3353
3450
  }
3354
3451
  await requireLimit2(deps, { accountId: grant.accountId, workspaceId, action: "document:index", quantity: 0 });
3355
3452
  const payload = AddDocumentRequest.parse(await c.req.json());
@@ -3399,19 +3496,19 @@ function registerDocumentRoutes(app, deps) {
3399
3496
  const workspaceId = c.req.param("workspaceId");
3400
3497
  const grant = await requireAccessGrant4(c, deps, workspaceId, "documents:manage");
3401
3498
  if (!objectStorage) {
3402
- throw new HTTPException6(503, { message: "object storage is not configured" });
3499
+ throw new HTTPException8(503, { message: "object storage is not configured" });
3403
3500
  }
3404
3501
  await requireLimit2(deps, { accountId: grant.accountId, workspaceId, action: "document:index", quantity: 0 });
3405
3502
  try {
3406
3503
  const document = await getDocument(db, workspaceId, c.req.param("documentId"));
3407
3504
  if (!document) {
3408
- throw new HTTPException6(404, { message: "document not found" });
3505
+ throw new HTTPException8(404, { message: "document not found" });
3409
3506
  }
3410
3507
  if (document.status !== "failed") {
3411
- throw new HTTPException6(422, { message: "only failed documents can be retried" });
3508
+ throw new HTTPException8(422, { message: "only failed documents can be retried" });
3412
3509
  }
3413
3510
  if (document.baseId !== c.req.param("baseId")) {
3414
- throw new HTTPException6(404, { message: "document not found" });
3511
+ throw new HTTPException8(404, { message: "document not found" });
3415
3512
  }
3416
3513
  const queued = await queueDocumentForReindex(db, workspaceId, document.id);
3417
3514
  const indexed = await documentIndexer.indexDocument({ accountId: grant.accountId, workspaceId, documentId: document.id }) ?? queued;
@@ -3430,7 +3527,7 @@ function registerDocumentRoutes(app, deps) {
3430
3527
  }
3431
3528
  return c.json(Document.parse(indexed));
3432
3529
  } catch (error) {
3433
- if (error instanceof HTTPException6) {
3530
+ if (error instanceof HTTPException8) {
3434
3531
  throw error;
3435
3532
  }
3436
3533
  throw documentHttpException(error);
@@ -3442,7 +3539,7 @@ function registerDocumentRoutes(app, deps) {
3442
3539
  const payload = DocumentSearchRequest.parse(await c.req.json());
3443
3540
  const base = await getDocumentBase(db, workspaceId, c.req.param("baseId"));
3444
3541
  if (!base) {
3445
- throw new HTTPException6(404, { message: "document base not found" });
3542
+ throw new HTTPException8(404, { message: "document base not found" });
3446
3543
  }
3447
3544
  return c.json({
3448
3545
  results: await searchDocuments2(db, {
@@ -3483,7 +3580,7 @@ function registerDocumentRoutes(app, deps) {
3483
3580
  limit: c.req.query("limit") ? Number(c.req.query("limit")) : void 0
3484
3581
  });
3485
3582
  if (!parsed.success) {
3486
- throw new HTTPException6(400, { message: "invalid knowledge memory query parameters" });
3583
+ throw new HTTPException8(400, { message: "invalid knowledge memory query parameters" });
3487
3584
  }
3488
3585
  return c.json((await listKnowledgeMemories2(db, workspaceId, parsed.data)).map((memory) => KnowledgeMemory.parse(memory)));
3489
3586
  });
@@ -3492,7 +3589,7 @@ function registerDocumentRoutes(app, deps) {
3492
3589
  await requireAccessGrant4(c, deps, workspaceId, "documents:search");
3493
3590
  const memory = await getKnowledgeMemory(db, workspaceId, c.req.param("memoryId"));
3494
3591
  if (!memory) {
3495
- throw new HTTPException6(404, { message: "knowledge memory not found" });
3592
+ throw new HTTPException8(404, { message: "knowledge memory not found" });
3496
3593
  }
3497
3594
  return c.json(KnowledgeMemory.parse(memory));
3498
3595
  });
@@ -3533,12 +3630,12 @@ function registerDocumentRoutes(app, deps) {
3533
3630
  function documentHttpException(error) {
3534
3631
  const message = error instanceof Error ? error.message : String(error);
3535
3632
  if (message.includes("not found")) {
3536
- return new HTTPException6(404, { message });
3633
+ return new HTTPException8(404, { message });
3537
3634
  }
3538
3635
  if (message.includes("pending") || message.includes("failed") || message.includes("deleted")) {
3539
- return new HTTPException6(422, { message });
3636
+ return new HTTPException8(422, { message });
3540
3637
  }
3541
- return new HTTPException6(500, { message });
3638
+ return new HTTPException8(500, { message });
3542
3639
  }
3543
3640
 
3544
3641
  // src/routes/enrollments.ts
@@ -3564,7 +3661,7 @@ import {
3564
3661
  listEnrollments,
3565
3662
  revokeEnrollment
3566
3663
  } from "@opengeni/db";
3567
- import { HTTPException as HTTPException7 } from "hono/http-exception";
3664
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
3568
3665
  import { requireAccessGrant as requireAccessGrant5 } from "@opengeni/core";
3569
3666
 
3570
3667
  // src/sandbox/enrollment.ts
@@ -3848,7 +3945,7 @@ function registerEnrollmentRoutes(app, deps) {
3848
3945
  const { settings, db } = deps;
3849
3946
  function assertSelfhostedEnabled() {
3850
3947
  if (!settings.sandboxSelfhostedEnabled) {
3851
- throw new HTTPException7(404, { message: "selfhosted enrollment is not enabled for this deployment" });
3948
+ throw new HTTPException9(404, { message: "selfhosted enrollment is not enabled for this deployment" });
3852
3949
  }
3853
3950
  }
3854
3951
  const startLimiter = new TokenBucket({ capacity: 10, refillPerSecond: 0.5 });
@@ -3858,7 +3955,7 @@ function registerEnrollmentRoutes(app, deps) {
3858
3955
  function rateLimit(c, limiter) {
3859
3956
  const ip = clientIp(c);
3860
3957
  if (!limiter.take(ip)) {
3861
- throw new HTTPException7(429, { message: "too many requests; slow down" });
3958
+ throw new HTTPException9(429, { message: "too many requests; slow down" });
3862
3959
  }
3863
3960
  }
3864
3961
  app.post("/v1/enrollments/device/start", async (c) => {
@@ -3866,12 +3963,12 @@ function registerEnrollmentRoutes(app, deps) {
3866
3963
  rateLimit(c, startLimiter);
3867
3964
  const parsed = DeviceEnrollmentStartRequest.safeParse(await c.req.json().catch(() => null));
3868
3965
  if (!parsed.success) {
3869
- throw new HTTPException7(400, { message: "invalid device-start request" });
3966
+ throw new HTTPException9(400, { message: "invalid device-start request" });
3870
3967
  }
3871
3968
  const body = parsed.data;
3872
3969
  const workspace = await getWorkspace(db, body.workspaceId);
3873
3970
  if (!workspace) {
3874
- throw new HTTPException7(404, { message: "workspace not found" });
3971
+ throw new HTTPException9(404, { message: "workspace not found" });
3875
3972
  }
3876
3973
  const result = await startDeviceEnrollment({ db, settings }, {
3877
3974
  accountId: workspace.accountId,
@@ -3892,7 +3989,7 @@ function registerEnrollmentRoutes(app, deps) {
3892
3989
  rateLimit(c, pollLimiter);
3893
3990
  const parsed = DeviceEnrollmentPollRequest.safeParse(await c.req.json().catch(() => null));
3894
3991
  if (!parsed.success) {
3895
- throw new HTTPException7(400, { message: "invalid device-poll request" });
3992
+ throw new HTTPException9(400, { message: "invalid device-poll request" });
3896
3993
  }
3897
3994
  const result = await pollDeviceEnrollment({ db, settings }, { deviceCode: parsed.data.deviceCode });
3898
3995
  return c.json(result, 200);
@@ -3902,16 +3999,16 @@ function registerEnrollmentRoutes(app, deps) {
3902
3999
  rateLimit(c, lookupLimiter);
3903
4000
  const parsed = DeviceEnrollmentLookupRequest.safeParse(await c.req.json().catch(() => null));
3904
4001
  if (!parsed.success) {
3905
- throw new HTTPException7(400, { message: "invalid device-lookup request" });
4002
+ throw new HTTPException9(400, { message: "invalid device-lookup request" });
3906
4003
  }
3907
4004
  const record3 = await lookupDeviceEnrollment({ db, settings }, { userCode: parsed.data.userCode });
3908
4005
  if (!record3) {
3909
- throw new HTTPException7(404, { message: "no pending enrollment for that code" });
4006
+ throw new HTTPException9(404, { message: "no pending enrollment for that code" });
3910
4007
  }
3911
4008
  try {
3912
4009
  await requireAccessGrant5(c, deps, record3.workspaceId, "enrollments:read");
3913
4010
  } catch {
3914
- throw new HTTPException7(404, { message: "no pending enrollment for that code" });
4011
+ throw new HTTPException9(404, { message: "no pending enrollment for that code" });
3915
4012
  }
3916
4013
  return c.json(DeviceEnrollmentLookupResponse.parse(toLookupResponse(record3)), 200);
3917
4014
  });
@@ -3920,7 +4017,7 @@ function registerEnrollmentRoutes(app, deps) {
3920
4017
  rateLimit(c, exchangeLimiter);
3921
4018
  const parsed = EnrollTokenExchangeRequest.safeParse(await c.req.json().catch(() => null));
3922
4019
  if (!parsed.success) {
3923
- throw new HTTPException7(400, { message: "invalid enroll-token-exchange request" });
4020
+ throw new HTTPException9(400, { message: "invalid enroll-token-exchange request" });
3924
4021
  }
3925
4022
  const body = parsed.data;
3926
4023
  const result = await exchangeEnrollToken({ db, settings }, {
@@ -3933,9 +4030,9 @@ function registerEnrollmentRoutes(app, deps) {
3933
4030
  });
3934
4031
  if (!result.ok) {
3935
4032
  if (result.reason === "disabled") {
3936
- throw new HTTPException7(503, { message: "enrollment credential plane is not configured" });
4033
+ throw new HTTPException9(503, { message: "enrollment credential plane is not configured" });
3937
4034
  }
3938
- throw new HTTPException7(401, { message: "invalid or expired enroll token" });
4035
+ throw new HTTPException9(401, { message: "invalid or expired enroll token" });
3939
4036
  }
3940
4037
  return c.json(EnrollTokenExchangeResponse.parse({ credentials: result.credentials }), 201);
3941
4038
  });
@@ -3945,7 +4042,7 @@ function registerEnrollmentRoutes(app, deps) {
3945
4042
  assertSelfhostedEnabled();
3946
4043
  const parsed = DeviceEnrollmentApproveRequest.safeParse(await c.req.json().catch(() => null));
3947
4044
  if (!parsed.success) {
3948
- throw new HTTPException7(400, { message: "invalid device-approve request" });
4045
+ throw new HTTPException9(400, { message: "invalid device-approve request" });
3949
4046
  }
3950
4047
  const body = parsed.data;
3951
4048
  const approved = await approveDeviceEnrollment({ db, settings }, {
@@ -3958,7 +4055,7 @@ function registerEnrollmentRoutes(app, deps) {
3958
4055
  approvedBySubjectLabel: grant.subjectLabel ?? null
3959
4056
  });
3960
4057
  if (!approved) {
3961
- throw new HTTPException7(404, { message: "no pending enrollment for that code" });
4058
+ throw new HTTPException9(404, { message: "no pending enrollment for that code" });
3962
4059
  }
3963
4060
  return c.json(DeviceEnrollmentApproveResponse.parse({
3964
4061
  approved: true,
@@ -3973,7 +4070,7 @@ function registerEnrollmentRoutes(app, deps) {
3973
4070
  assertSelfhostedEnabled();
3974
4071
  const parsed = DeviceEnrollmentDenyRequest.safeParse(await c.req.json().catch(() => null));
3975
4072
  if (!parsed.success) {
3976
- throw new HTTPException7(400, { message: "invalid device-deny request" });
4073
+ throw new HTTPException9(400, { message: "invalid device-deny request" });
3977
4074
  }
3978
4075
  const result = await denyDeviceEnrollment({ db, settings }, {
3979
4076
  accountId: grant.accountId,
@@ -3988,7 +4085,7 @@ function registerEnrollmentRoutes(app, deps) {
3988
4085
  assertSelfhostedEnabled();
3989
4086
  const parsed = MintEnrollTokenRequest.safeParse(await c.req.json().catch(() => ({})));
3990
4087
  if (!parsed.success) {
3991
- throw new HTTPException7(400, { message: "invalid mint-enroll-token request" });
4088
+ throw new HTTPException9(400, { message: "invalid mint-enroll-token request" });
3992
4089
  }
3993
4090
  const minted = await mintEnrollToken({ db, settings }, {
3994
4091
  accountId: grant.accountId,
@@ -3996,7 +4093,7 @@ function registerEnrollmentRoutes(app, deps) {
3996
4093
  allowScreenControl: parsed.data.allowScreenControl
3997
4094
  });
3998
4095
  if (!minted) {
3999
- throw new HTTPException7(503, { message: "enrollment credential plane is not configured" });
4096
+ throw new HTTPException9(503, { message: "enrollment credential plane is not configured" });
4000
4097
  }
4001
4098
  return c.json(MintEnrollTokenResponse.parse(minted), 201);
4002
4099
  });
@@ -4080,7 +4177,7 @@ import {
4080
4177
  getEnrollment as getEnrollment2,
4081
4178
  readMachineMetricsSeries
4082
4179
  } from "@opengeni/db";
4083
- import { HTTPException as HTTPException8 } from "hono/http-exception";
4180
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
4084
4181
  import { requireAccessGrant as requireAccessGrant6 } from "@opengeni/core";
4085
4182
  import { buildFleetContextForSession as buildFleetContextForSession2, swapActiveSandbox as swapActiveSandbox2 } from "@opengeni/core";
4086
4183
 
@@ -4255,7 +4352,7 @@ function registerMachineRoutes(app, deps) {
4255
4352
  const { settings, db, bus } = deps;
4256
4353
  function assertSelfhostedEnabled() {
4257
4354
  if (!settings.sandboxSelfhostedEnabled) {
4258
- throw new HTTPException8(404, { message: "selfhosted machines are not enabled for this deployment" });
4355
+ throw new HTTPException10(404, { message: "selfhosted machines are not enabled for this deployment" });
4259
4356
  }
4260
4357
  }
4261
4358
  app.get("/v1/workspaces/:workspaceId/machines", async (c) => {
@@ -4273,7 +4370,7 @@ function registerMachineRoutes(app, deps) {
4273
4370
  const enrollmentId = c.req.param("enrollmentId");
4274
4371
  const enrollment = await getEnrollment2(db, workspaceId, enrollmentId);
4275
4372
  if (!enrollment) {
4276
- throw new HTTPException8(404, { message: "machine not found in this workspace" });
4373
+ throw new HTTPException10(404, { message: "machine not found in this workspace" });
4277
4374
  }
4278
4375
  const windowMs = SERIES_WINDOWS_MS[c.req.query("window") ?? ""] ?? DEFAULT_SERIES_WINDOW_MS;
4279
4376
  const since = new Date(Date.now() - windowMs);
@@ -4318,7 +4415,7 @@ import {
4318
4415
  setWorkspaceEnvironmentVariable as setWorkspaceEnvironmentVariable2,
4319
4416
  updateWorkspaceEnvironment
4320
4417
  } from "@opengeni/db";
4321
- import { HTTPException as HTTPException9 } from "hono/http-exception";
4418
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
4322
4419
  import { requireAccessGrant as requireAccessGrant7 } from "@opengeni/core";
4323
4420
  import {
4324
4421
  assertAllowedEnvironmentVariableName as assertAllowedEnvironmentVariableName2,
@@ -4342,21 +4439,21 @@ function registerEnvironmentRoutes(app, deps) {
4342
4439
  const payload = CreateWorkspaceEnvironmentRequest.parse(await c.req.json());
4343
4440
  const name = trimmedEnvironmentName(payload.name);
4344
4441
  if (payload.variables.length > MAX_VARIABLES_PER_ENVIRONMENT2) {
4345
- throw new HTTPException9(422, { message: `an environment supports at most ${MAX_VARIABLES_PER_ENVIRONMENT2} variables` });
4442
+ throw new HTTPException11(422, { message: `an environment supports at most ${MAX_VARIABLES_PER_ENVIRONMENT2} variables` });
4346
4443
  }
4347
4444
  const variableNames = /* @__PURE__ */ new Set();
4348
4445
  for (const variable of payload.variables) {
4349
4446
  assertAllowedEnvironmentVariableName2(variable.name);
4350
4447
  if (variableNames.has(variable.name)) {
4351
- throw new HTTPException9(422, { message: `duplicate environment variable name: ${variable.name}` });
4448
+ throw new HTTPException11(422, { message: `duplicate environment variable name: ${variable.name}` });
4352
4449
  }
4353
4450
  variableNames.add(variable.name);
4354
4451
  }
4355
4452
  if (await countWorkspaceEnvironments2(db, workspaceId) >= MAX_ENVIRONMENTS_PER_WORKSPACE2) {
4356
- throw new HTTPException9(422, { message: `a workspace supports at most ${MAX_ENVIRONMENTS_PER_WORKSPACE2} environments` });
4453
+ throw new HTTPException11(422, { message: `a workspace supports at most ${MAX_ENVIRONMENTS_PER_WORKSPACE2} environments` });
4357
4454
  }
4358
4455
  if (await getWorkspaceEnvironmentByName2(db, workspaceId, name)) {
4359
- throw new HTTPException9(409, { message: `environment name is already in use: ${name}` });
4456
+ throw new HTTPException11(409, { message: `environment name is already in use: ${name}` });
4360
4457
  }
4361
4458
  const created = await createWorkspaceEnvironment2(db, {
4362
4459
  accountId: grant.accountId,
@@ -4385,7 +4482,7 @@ function registerEnvironmentRoutes(app, deps) {
4385
4482
  if (name !== void 0 && name !== environment.name) {
4386
4483
  const existing = await getWorkspaceEnvironmentByName2(db, workspaceId, name);
4387
4484
  if (existing && existing.id !== environment.id) {
4388
- throw new HTTPException9(409, { message: `environment name is already in use: ${name}` });
4485
+ throw new HTTPException11(409, { message: `environment name is already in use: ${name}` });
4389
4486
  }
4390
4487
  }
4391
4488
  const updated = await updateWorkspaceEnvironment(db, workspaceId, environment.id, {
@@ -4401,11 +4498,11 @@ function registerEnvironmentRoutes(app, deps) {
4401
4498
  const environment = await requireEnvironmentForApi(db, workspaceId, c.req.param("environmentId"));
4402
4499
  const attachedTasks = await countScheduledTasksUsingEnvironment(db, workspaceId, environment.id);
4403
4500
  if (attachedTasks > 0) {
4404
- throw new HTTPException9(409, { message: `environment is attached to ${attachedTasks} scheduled task(s); detach first` });
4501
+ throw new HTTPException11(409, { message: `environment is attached to ${attachedTasks} scheduled task(s); detach first` });
4405
4502
  }
4406
4503
  const activeSessions = await countActiveSessionsUsingEnvironment(db, workspaceId, environment.id);
4407
4504
  if (activeSessions > 0) {
4408
- throw new HTTPException9(409, { message: `environment is attached to ${activeSessions} active session(s); wait for them to finish or cancel them first` });
4505
+ throw new HTTPException11(409, { message: `environment is attached to ${activeSessions} active session(s); wait for them to finish or cancel them first` });
4409
4506
  }
4410
4507
  await deleteWorkspaceEnvironment(db, workspaceId, environment.id);
4411
4508
  await recordEnvironmentAuditEvent2(db, { grant, action: "environment.deleted", environmentId: environment.id });
@@ -4420,7 +4517,7 @@ function registerEnvironmentRoutes(app, deps) {
4420
4517
  const payload = SetWorkspaceEnvironmentVariableRequest.parse(await c.req.json());
4421
4518
  const exists = environment.variables.some((variable) => variable.name === name);
4422
4519
  if (!exists && environment.variables.length >= MAX_VARIABLES_PER_ENVIRONMENT2) {
4423
- throw new HTTPException9(422, { message: `an environment supports at most ${MAX_VARIABLES_PER_ENVIRONMENT2} variables` });
4520
+ throw new HTTPException11(422, { message: `an environment supports at most ${MAX_VARIABLES_PER_ENVIRONMENT2} variables` });
4424
4521
  }
4425
4522
  const metadata = await setWorkspaceEnvironmentVariable2(db, {
4426
4523
  accountId: grant.accountId,
@@ -4439,7 +4536,7 @@ function registerEnvironmentRoutes(app, deps) {
4439
4536
  const environment = await requireEnvironmentForApi(db, workspaceId, c.req.param("environmentId"));
4440
4537
  const deleted = await deleteWorkspaceEnvironmentVariable(db, workspaceId, environment.id, name);
4441
4538
  if (!deleted) {
4442
- throw new HTTPException9(404, { message: "environment variable not found" });
4539
+ throw new HTTPException11(404, { message: "environment variable not found" });
4443
4540
  }
4444
4541
  await recordEnvironmentAuditEvent2(db, { grant, action: "environment.variable.deleted", environmentId: environment.id, variableName: name });
4445
4542
  return c.json({ ok: true });
@@ -4448,7 +4545,7 @@ function registerEnvironmentRoutes(app, deps) {
4448
4545
  function parseVariableName(raw) {
4449
4546
  const parsed = WorkspaceEnvironmentVariableName2.safeParse(raw);
4450
4547
  if (!parsed.success) {
4451
- throw new HTTPException9(422, { message: "environment variable names must match ^[A-Z][A-Z0-9_]*$" });
4548
+ throw new HTTPException11(422, { message: "environment variable names must match ^[A-Z][A-Z0-9_]*$" });
4452
4549
  }
4453
4550
  assertAllowedEnvironmentVariableName2(parsed.data);
4454
4551
  return parsed.data;
@@ -4456,7 +4553,7 @@ function parseVariableName(raw) {
4456
4553
  function trimmedEnvironmentName(name) {
4457
4554
  const trimmed = name.trim();
4458
4555
  if (!trimmed) {
4459
- throw new HTTPException9(422, { message: "environment name is required" });
4556
+ throw new HTTPException11(422, { message: "environment name is required" });
4460
4557
  }
4461
4558
  return trimmed;
4462
4559
  }
@@ -4476,7 +4573,7 @@ import {
4476
4573
  markFileUploadFailed,
4477
4574
  requireFile as requireFile2
4478
4575
  } from "@opengeni/db";
4479
- import { HTTPException as HTTPException10 } from "hono/http-exception";
4576
+ import { HTTPException as HTTPException12 } from "hono/http-exception";
4480
4577
  import { requireAccessGrant as requireAccessGrant8 } from "@opengeni/core";
4481
4578
  import { recordWorkspaceUsage as recordWorkspaceUsage3, requireLimit as requireLimit3 } from "@opengeni/core";
4482
4579
  function registerFileRoutes(app, deps) {
@@ -4485,12 +4582,12 @@ function registerFileRoutes(app, deps) {
4485
4582
  const workspaceId = c.req.param("workspaceId");
4486
4583
  const grant = await requireAccessGrant8(c, deps, workspaceId, "files:upload");
4487
4584
  if (!objectStorage) {
4488
- throw new HTTPException10(503, { message: "object storage is not configured" });
4585
+ throw new HTTPException12(503, { message: "object storage is not configured" });
4489
4586
  }
4490
4587
  const payload = CreateFileUploadRequest.parse(await c.req.json());
4491
4588
  await requireLimit3(deps, { accountId: grant.accountId, workspaceId, action: "file:upload", quantity: payload.sizeBytes });
4492
4589
  if (payload.sizeBytes > objectStorage.maxSinglePutSizeBytes) {
4493
- throw new HTTPException10(413, { message: `file exceeds single PUT limit of ${objectStorage.maxSinglePutSizeBytes} bytes` });
4590
+ throw new HTTPException12(413, { message: `file exceeds single PUT limit of ${objectStorage.maxSinglePutSizeBytes} bytes` });
4494
4591
  }
4495
4592
  const fileId = crypto.randomUUID();
4496
4593
  const safeFilename = sanitizeFilename(payload.filename);
@@ -4526,33 +4623,33 @@ function registerFileRoutes(app, deps) {
4526
4623
  const workspaceId = c.req.param("workspaceId");
4527
4624
  const grant = await requireAccessGrant8(c, deps, workspaceId, "files:upload");
4528
4625
  if (!objectStorage) {
4529
- throw new HTTPException10(503, { message: "object storage is not configured" });
4626
+ throw new HTTPException12(503, { message: "object storage is not configured" });
4530
4627
  }
4531
4628
  const upload = await getFileUpload(db, workspaceId, c.req.param("uploadId"));
4532
4629
  if (!upload) {
4533
- throw new HTTPException10(404, { message: "file upload not found" });
4630
+ throw new HTTPException12(404, { message: "file upload not found" });
4534
4631
  }
4535
4632
  if (upload.status !== "pending") {
4536
- throw new HTTPException10(409, { message: `file upload is ${upload.status}` });
4633
+ throw new HTTPException12(409, { message: `file upload is ${upload.status}` });
4537
4634
  }
4538
4635
  if (upload.expiresAt.getTime() < Date.now()) {
4539
4636
  await markFileUploadFailed(db, workspaceId, upload.id, upload.file.id);
4540
- throw new HTTPException10(409, { message: "file upload has expired" });
4637
+ throw new HTTPException12(409, { message: "file upload has expired" });
4541
4638
  }
4542
4639
  const head = await objectStorage.headFile(upload.file).catch((error) => {
4543
- throw new HTTPException10(409, { message: `uploaded object is not available: ${error instanceof Error ? error.message : String(error)}` });
4640
+ throw new HTTPException12(409, { message: `uploaded object is not available: ${error instanceof Error ? error.message : String(error)}` });
4544
4641
  });
4545
4642
  if (Number(head.ContentLength ?? -1) !== upload.file.sizeBytes) {
4546
4643
  await markFileUploadFailed(db, workspaceId, upload.id, upload.file.id);
4547
- throw new HTTPException10(422, { message: "uploaded object size does not match file metadata" });
4644
+ throw new HTTPException12(422, { message: "uploaded object size does not match file metadata" });
4548
4645
  }
4549
4646
  if (upload.file.contentType && head.ContentType && head.ContentType !== upload.file.contentType) {
4550
4647
  await markFileUploadFailed(db, workspaceId, upload.id, upload.file.id);
4551
- throw new HTTPException10(422, { message: "uploaded object content type does not match file metadata" });
4648
+ throw new HTTPException12(422, { message: "uploaded object content type does not match file metadata" });
4552
4649
  }
4553
4650
  if (upload.file.sha256 && head.Metadata?.sha256 !== upload.file.sha256) {
4554
4651
  await markFileUploadFailed(db, workspaceId, upload.id, upload.file.id);
4555
- throw new HTTPException10(422, { message: "uploaded object checksum metadata does not match file metadata" });
4652
+ throw new HTTPException12(422, { message: "uploaded object checksum metadata does not match file metadata" });
4556
4653
  }
4557
4654
  const file = await completeFileUpload(db, workspaceId, upload.id);
4558
4655
  await recordWorkspaceUsage3(deps, {
@@ -4573,7 +4670,7 @@ function registerFileRoutes(app, deps) {
4573
4670
  await requireAccessGrant8(c, deps, workspaceId, "files:read");
4574
4671
  const file = await requireFile2(db, workspaceId, c.req.param("fileId")).catch(() => null);
4575
4672
  if (!file) {
4576
- throw new HTTPException10(404, { message: "file not found" });
4673
+ throw new HTTPException12(404, { message: "file not found" });
4577
4674
  }
4578
4675
  return c.json(FileAsset.parse(file));
4579
4676
  });
@@ -4581,14 +4678,14 @@ function registerFileRoutes(app, deps) {
4581
4678
  const workspaceId = c.req.param("workspaceId");
4582
4679
  await requireAccessGrant8(c, deps, workspaceId, "files:read");
4583
4680
  if (!objectStorage) {
4584
- throw new HTTPException10(503, { message: "object storage is not configured" });
4681
+ throw new HTTPException12(503, { message: "object storage is not configured" });
4585
4682
  }
4586
4683
  const file = await requireFile2(db, workspaceId, c.req.param("fileId")).catch(() => null);
4587
4684
  if (!file) {
4588
- throw new HTTPException10(404, { message: "file not found" });
4685
+ throw new HTTPException12(404, { message: "file not found" });
4589
4686
  }
4590
4687
  if (file.status !== "ready") {
4591
- throw new HTTPException10(409, { message: `file is ${file.status}` });
4688
+ throw new HTTPException12(409, { message: `file is ${file.status}` });
4592
4689
  }
4593
4690
  const signed = await objectStorage.createGetUrl({ key: file.objectKey });
4594
4691
  return c.json(FileDownloadUrlResponse.parse({
@@ -4607,7 +4704,7 @@ function sanitizeFilename(filename) {
4607
4704
  import { CreateApiKeyRequest, CreateApiKeyResponse } from "@opengeni/contracts";
4608
4705
  import { createApiKey, listApiKeys, revokeApiKey } from "@opengeni/db";
4609
4706
  import { zValidator } from "@hono/zod-validator";
4610
- import { HTTPException as HTTPException11 } from "hono/http-exception";
4707
+ import { HTTPException as HTTPException13 } from "hono/http-exception";
4611
4708
  import { requireAccessGrant as requireAccessGrant9 } from "@opengeni/core";
4612
4709
  import { requireLimit as requireLimit4 } from "@opengeni/core";
4613
4710
  function registerApiKeyRoutes(app, deps) {
@@ -4648,7 +4745,7 @@ function ensureDelegablePermissions(grantPermissions, requested) {
4648
4745
  }
4649
4746
  const missing = requested.filter((permission) => !grantPermissions.includes(permission));
4650
4747
  if (missing.length > 0) {
4651
- throw new HTTPException11(403, { message: `cannot delegate missing permissions: ${missing.join(", ")}` });
4748
+ throw new HTTPException13(403, { message: `cannot delegate missing permissions: ${missing.join(", ")}` });
4652
4749
  }
4653
4750
  }
4654
4751
  function generateApiKeyToken() {
@@ -4680,7 +4777,7 @@ import {
4680
4777
  recordStripeWebhookEvent,
4681
4778
  upsertBillingCustomer
4682
4779
  } from "@opengeni/db";
4683
- import { HTTPException as HTTPException12 } from "hono/http-exception";
4780
+ import { HTTPException as HTTPException14 } from "hono/http-exception";
4684
4781
  import Stripe from "stripe";
4685
4782
  import { requireAccessContext } from "@opengeni/core";
4686
4783
  function registerBillingRoutes(app, deps) {
@@ -4694,7 +4791,7 @@ function registerBillingRoutes(app, deps) {
4694
4791
  const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
4695
4792
  const workspaceId = c.req.query("workspaceId");
4696
4793
  if (workspaceId && !context.workspaceGrants.some((grant) => grant.accountId === accountId && grant.workspaceId === workspaceId)) {
4697
- throw new HTTPException12(403, { message: "missing workspace access for usage query" });
4794
+ throw new HTTPException14(403, { message: "missing workspace access for usage query" });
4698
4795
  }
4699
4796
  return c.json({
4700
4797
  balance: await getBillingBalance(deps.db, accountId),
@@ -4712,12 +4809,12 @@ function registerBillingRoutes(app, deps) {
4712
4809
  });
4713
4810
  app.post("/v1/billing/checkout", async (c) => {
4714
4811
  if (deps.settings.billingMode !== "stripe") {
4715
- throw new HTTPException12(404, { message: "stripe billing is not enabled" });
4812
+ throw new HTTPException14(404, { message: "stripe billing is not enabled" });
4716
4813
  }
4717
4814
  const context = await requireAccessContext(c, deps);
4718
4815
  const parsed = CreateCheckoutRequest.safeParse(await c.req.json());
4719
4816
  if (!parsed.success) {
4720
- throw new HTTPException12(400, { message: parsed.error.issues[0]?.message ?? "invalid checkout request" });
4817
+ throw new HTTPException14(400, { message: parsed.error.issues[0]?.message ?? "invalid checkout request" });
4721
4818
  }
4722
4819
  const body = parsed.data;
4723
4820
  const accountId = requireSelectedAccount(context, body.accountId, "billing:manage");
@@ -4738,7 +4835,7 @@ function registerBillingRoutes(app, deps) {
4738
4835
  idempotencyKey
4739
4836
  }), { idempotencyKey });
4740
4837
  if (!session.url) {
4741
- throw new HTTPException12(502, { message: "Stripe did not return a checkout URL" });
4838
+ throw new HTTPException14(502, { message: "Stripe did not return a checkout URL" });
4742
4839
  }
4743
4840
  return c.json(CreateCheckoutResponse.parse({
4744
4841
  checkoutSessionId: session.id,
@@ -4747,18 +4844,18 @@ function registerBillingRoutes(app, deps) {
4747
4844
  });
4748
4845
  app.post("/v1/webhooks/stripe", async (c) => {
4749
4846
  if (deps.settings.billingMode !== "stripe") {
4750
- throw new HTTPException12(404, { message: "stripe billing is not enabled" });
4847
+ throw new HTTPException14(404, { message: "stripe billing is not enabled" });
4751
4848
  }
4752
4849
  const signature = c.req.header("stripe-signature");
4753
4850
  if (!signature) {
4754
- throw new HTTPException12(400, { message: "missing stripe-signature" });
4851
+ throw new HTTPException14(400, { message: "missing stripe-signature" });
4755
4852
  }
4756
4853
  const payload = await c.req.text();
4757
4854
  let event;
4758
4855
  try {
4759
4856
  event = await stripeClient(deps).webhooks.constructEventAsync(payload, signature, deps.settings.stripeWebhookSecret);
4760
4857
  } catch (error) {
4761
- throw new HTTPException12(400, { message: error instanceof Error ? error.message : "invalid stripe signature" });
4858
+ throw new HTTPException14(400, { message: error instanceof Error ? error.message : "invalid stripe signature" });
4762
4859
  }
4763
4860
  const firstSeen = await recordStripeWebhookEvent(deps.db, {
4764
4861
  id: event.id,
@@ -4776,7 +4873,7 @@ function registerBillingRoutes(app, deps) {
4776
4873
  await markStripeWebhookProcessed(deps.db, event.id);
4777
4874
  return c.json({ received: true });
4778
4875
  } catch (error) {
4779
- throw new HTTPException12(500, { message: error instanceof Error ? error.message : String(error) });
4876
+ throw new HTTPException14(500, { message: error instanceof Error ? error.message : String(error) });
4780
4877
  }
4781
4878
  });
4782
4879
  }
@@ -4828,7 +4925,7 @@ function stripeCheckoutSessionCreateParams(input) {
4828
4925
  }
4829
4926
  function checkoutReturnUrl(publicBaseUrl, candidate, fallbackPath, field) {
4830
4927
  if (!publicBaseUrl) {
4831
- throw new HTTPException12(500, { message: "OPENGENI_PUBLIC_BASE_URL is required for Stripe checkout" });
4928
+ throw new HTTPException14(500, { message: "OPENGENI_PUBLIC_BASE_URL is required for Stripe checkout" });
4832
4929
  }
4833
4930
  const base = new URL(publicBaseUrl);
4834
4931
  const fallback = new URL(fallbackPath, base).toString();
@@ -4837,7 +4934,7 @@ function checkoutReturnUrl(publicBaseUrl, candidate, fallbackPath, field) {
4837
4934
  }
4838
4935
  const parsed = new URL(candidate);
4839
4936
  if (parsed.origin !== base.origin) {
4840
- throw new HTTPException12(400, { message: `${field} must use the OpenGeni public origin` });
4937
+ throw new HTTPException14(400, { message: `${field} must use the OpenGeni public origin` });
4841
4938
  }
4842
4939
  return parsed.toString();
4843
4940
  }
@@ -5069,7 +5166,7 @@ async function getOrCreateStripeCustomer(deps, stripe, context, accountId) {
5069
5166
  }
5070
5167
  const account = await getManagedAccount(deps.db, accountId);
5071
5168
  if (!account) {
5072
- throw new HTTPException12(404, { message: "account not found" });
5169
+ throw new HTTPException14(404, { message: "account not found" });
5073
5170
  }
5074
5171
  const customer = await stripe.customers.create({
5075
5172
  name: account.name,
@@ -5095,17 +5192,17 @@ function stripeCustomerProvider(input) {
5095
5192
  function requireSelectedAccount(context, requested, permission) {
5096
5193
  const accountId = requested ?? context.defaultAccountId ?? void 0;
5097
5194
  if (!accountId) {
5098
- throw new HTTPException12(409, { message: "account selection is required" });
5195
+ throw new HTTPException14(409, { message: "account selection is required" });
5099
5196
  }
5100
5197
  const grant = context.accountGrants.find((candidate) => candidate.accountId === accountId);
5101
5198
  if (!grant || !grant.permissions.includes(permission) && !grant.permissions.includes("account:admin")) {
5102
- throw new HTTPException12(403, { message: `missing permission: ${permission}` });
5199
+ throw new HTTPException14(403, { message: `missing permission: ${permission}` });
5103
5200
  }
5104
5201
  return accountId;
5105
5202
  }
5106
5203
  function stripeClient(deps) {
5107
5204
  if (!deps.settings.stripeSecretKey) {
5108
- throw new HTTPException12(500, { message: "Stripe secret key is not configured" });
5205
+ throw new HTTPException14(500, { message: "Stripe secret key is not configured" });
5109
5206
  }
5110
5207
  return new Stripe(deps.settings.stripeSecretKey);
5111
5208
  }
@@ -5149,7 +5246,7 @@ import {
5149
5246
  verifySignedState
5150
5247
  } from "@opengeni/github";
5151
5248
  import { deleteCookie, getCookie, setCookie } from "hono/cookie";
5152
- import { HTTPException as HTTPException13 } from "hono/http-exception";
5249
+ import { HTTPException as HTTPException15 } from "hono/http-exception";
5153
5250
  import { requireAccessGrant as requireAccessGrant10 } from "@opengeni/core";
5154
5251
  var githubStateCookie = "opengeni_github_state";
5155
5252
  function registerGitHubRoutes(app, deps) {
@@ -5177,15 +5274,15 @@ function registerGitHubRoutes(app, deps) {
5177
5274
  const workspaceId = c.req.param("workspaceId");
5178
5275
  const state = c.req.query("state");
5179
5276
  if (!state) {
5180
- throw new HTTPException13(400, { message: "missing GitHub installation state" });
5277
+ throw new HTTPException15(400, { message: "missing GitHub installation state" });
5181
5278
  }
5182
5279
  const statePayload = readSignedState3(state, githubStateSecret);
5183
5280
  if (!statePayload || statePayload.workspaceId !== workspaceId) {
5184
- throw new HTTPException13(400, { message: "invalid or expired GitHub installation state" });
5281
+ throw new HTTPException15(400, { message: "invalid or expired GitHub installation state" });
5185
5282
  }
5186
5283
  const slug = settings.githubAppSlug?.trim();
5187
5284
  if (!slug) {
5188
- throw new HTTPException13(409, { message: JSON.stringify({ message: "GitHub App is not configured", missing: githubAppMissingSettings2(settings) }) });
5285
+ throw new HTTPException15(409, { message: JSON.stringify({ message: "GitHub App is not configured", missing: githubAppMissingSettings2(settings) }) });
5189
5286
  }
5190
5287
  setGitHubStateCookie(c, deps, state);
5191
5288
  return c.redirect(`https://github.com/apps/${slug}/installations/new?state=${encodeURIComponent(state)}`);
@@ -5197,9 +5294,9 @@ function registerGitHubRoutes(app, deps) {
5197
5294
  return c.json({ repositories: await listWorkspaceGitHubRepositories(deps, workspaceId) });
5198
5295
  } catch (error) {
5199
5296
  if (error instanceof GitHubAppConfigurationError2) {
5200
- throw new HTTPException13(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5297
+ throw new HTTPException15(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5201
5298
  }
5202
- throw new HTTPException13(502, { message: error instanceof Error ? error.message : String(error) });
5299
+ throw new HTTPException15(502, { message: error instanceof Error ? error.message : String(error) });
5203
5300
  }
5204
5301
  });
5205
5302
  app.post("/v1/workspaces/:workspaceId/github/repositories/sync", async (c) => {
@@ -5209,9 +5306,9 @@ function registerGitHubRoutes(app, deps) {
5209
5306
  return c.json({ repositories: await listWorkspaceGitHubRepositories(deps, workspaceId) });
5210
5307
  } catch (error) {
5211
5308
  if (error instanceof GitHubAppConfigurationError2) {
5212
- throw new HTTPException13(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5309
+ throw new HTTPException15(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5213
5310
  }
5214
- throw new HTTPException13(502, { message: error instanceof Error ? error.message : String(error) });
5311
+ throw new HTTPException15(502, { message: error instanceof Error ? error.message : String(error) });
5215
5312
  }
5216
5313
  });
5217
5314
  app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
@@ -5243,10 +5340,10 @@ function registerGitHubRoutes(app, deps) {
5243
5340
  const code = c.req.query("code");
5244
5341
  const state = c.req.query("state");
5245
5342
  if (!code) {
5246
- throw new HTTPException13(400, { message: "missing GitHub manifest code" });
5343
+ throw new HTTPException15(400, { message: "missing GitHub manifest code" });
5247
5344
  }
5248
5345
  if (!state || !verifySignedState(state, githubStateSecret)) {
5249
- throw new HTTPException13(400, { message: "invalid or expired GitHub manifest state" });
5346
+ throw new HTTPException15(400, { message: "invalid or expired GitHub manifest state" });
5250
5347
  }
5251
5348
  try {
5252
5349
  const conversion = await convertGitHubAppManifest(code);
@@ -5257,7 +5354,7 @@ function registerGitHubRoutes(app, deps) {
5257
5354
  return c.html(githubSuccessHtml(envLines, installUrl));
5258
5355
  } catch (error) {
5259
5356
  const message = error instanceof GitHubAppApiError ? error.message : String(error);
5260
- throw new HTTPException13(502, { message });
5357
+ throw new HTTPException15(502, { message });
5261
5358
  }
5262
5359
  });
5263
5360
  const handleGitHubInstallCallback = async (c) => {
@@ -5266,28 +5363,28 @@ function registerGitHubRoutes(app, deps) {
5266
5363
  const installationIdRaw = c.req.query("installation_id");
5267
5364
  const setupAction = c.req.query("setup_action") ?? null;
5268
5365
  if (!state) {
5269
- throw new HTTPException13(400, { message: "missing GitHub installation state" });
5366
+ throw new HTTPException15(400, { message: "missing GitHub installation state" });
5270
5367
  }
5271
5368
  const statePayload = readSignedState3(state, githubStateSecret);
5272
5369
  if (!statePayload || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string") {
5273
- throw new HTTPException13(400, { message: "invalid or expired GitHub installation state" });
5370
+ throw new HTTPException15(400, { message: "invalid or expired GitHub installation state" });
5274
5371
  }
5275
5372
  requireGitHubStateCookie(c, state);
5276
5373
  const grant = await requireAccessGrant10(c, deps, statePayload.workspaceId, "github:manage");
5277
5374
  if (grant.accountId !== statePayload.accountId) {
5278
- throw new HTTPException13(403, { message: "GitHub installation state does not match this workspace" });
5375
+ throw new HTTPException15(403, { message: "GitHub installation state does not match this workspace" });
5279
5376
  }
5280
5377
  if (setupAction === "request" && !installationIdRaw) {
5281
5378
  return c.html(githubSetupPendingHtml());
5282
5379
  }
5283
5380
  const installationId = parsePositiveInteger(installationIdRaw);
5284
5381
  if (installationId === null) {
5285
- throw new HTTPException13(400, { message: "missing or invalid GitHub installation_id" });
5382
+ throw new HTTPException15(400, { message: "missing or invalid GitHub installation_id" });
5286
5383
  }
5287
5384
  if (!code) {
5288
5385
  const clientId = settings.githubClientId?.trim();
5289
5386
  if (!clientId) {
5290
- throw new HTTPException13(409, { message: JSON.stringify({ message: "GitHub App is not configured", missing: ["OPENGENI_GITHUB_CLIENT_ID"] }) });
5387
+ throw new HTTPException15(409, { message: JSON.stringify({ message: "GitHub App is not configured", missing: ["OPENGENI_GITHUB_CLIENT_ID"] }) });
5291
5388
  }
5292
5389
  const oauthState = createSignedState4(githubStateSecret, {
5293
5390
  accountId: grant.accountId,
@@ -5314,15 +5411,15 @@ function registerGitHubRoutes(app, deps) {
5314
5411
  const code = c.req.query("code");
5315
5412
  const state = c.req.query("state");
5316
5413
  if (!code) {
5317
- throw new HTTPException13(400, { message: "missing GitHub OAuth code" });
5414
+ throw new HTTPException15(400, { message: "missing GitHub OAuth code" });
5318
5415
  }
5319
5416
  if (!state) {
5320
- throw new HTTPException13(400, { message: "missing GitHub OAuth state" });
5417
+ throw new HTTPException15(400, { message: "missing GitHub OAuth state" });
5321
5418
  }
5322
5419
  const statePayload = readSignedState3(state, githubStateSecret);
5323
5420
  const installationId = parsePositiveInteger(String(statePayload?.installationId ?? ""));
5324
5421
  if (!statePayload || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || installationId === null) {
5325
- throw new HTTPException13(400, { message: "invalid or expired GitHub OAuth state" });
5422
+ throw new HTTPException15(400, { message: "invalid or expired GitHub OAuth state" });
5326
5423
  }
5327
5424
  requireGitHubStateCookie(c, state);
5328
5425
  return await completeGitHubInstallationBinding(deps, c, {
@@ -5335,19 +5432,19 @@ function registerGitHubRoutes(app, deps) {
5335
5432
  async function completeGitHubInstallationBinding(deps, c, input) {
5336
5433
  const { db, settings } = deps;
5337
5434
  if (!input.statePayload.workspaceId || !input.statePayload.accountId) {
5338
- throw new HTTPException13(400, { message: "invalid or expired GitHub installation state" });
5435
+ throw new HTTPException15(400, { message: "invalid or expired GitHub installation state" });
5339
5436
  }
5340
5437
  const grant = await requireAccessGrant10(c, deps, input.statePayload.workspaceId, "github:manage");
5341
5438
  if (grant.accountId !== input.statePayload.accountId) {
5342
- throw new HTTPException13(403, { message: "GitHub installation state does not match this workspace" });
5439
+ throw new HTTPException15(403, { message: "GitHub installation state does not match this workspace" });
5343
5440
  }
5344
5441
  try {
5345
5442
  const installation = await verifyGitHubInstallationAccessForUser(settings, { code: input.code, installationId: input.installationId });
5346
5443
  if (!installation) {
5347
- throw new HTTPException13(404, { message: "GitHub App installation was not found for this app" });
5444
+ throw new HTTPException15(404, { message: "GitHub App installation was not found for this app" });
5348
5445
  }
5349
5446
  if (installation.suspended) {
5350
- throw new HTTPException13(409, { message: "GitHub App installation is suspended" });
5447
+ throw new HTTPException15(409, { message: "GitHub App installation is suspended" });
5351
5448
  }
5352
5449
  await upsertGitHubInstallation(db, {
5353
5450
  accountId: grant.accountId,
@@ -5360,13 +5457,13 @@ async function completeGitHubInstallationBinding(deps, c, input) {
5360
5457
  deleteCookie(c, githubStateCookie, { path: "/v1/github" });
5361
5458
  return c.html(githubSetupSuccessHtml(installation.accountLogin ?? `installation ${input.installationId}`, returnUrl));
5362
5459
  } catch (error) {
5363
- if (error instanceof HTTPException13) {
5460
+ if (error instanceof HTTPException15) {
5364
5461
  throw error;
5365
5462
  }
5366
5463
  if (error instanceof GitHubAppConfigurationError2) {
5367
- throw new HTTPException13(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5464
+ throw new HTTPException15(409, { message: JSON.stringify({ message: error.message, missing: error.missing }) });
5368
5465
  }
5369
- throw new HTTPException13(502, { message: error instanceof Error ? error.message : String(error) });
5466
+ throw new HTTPException15(502, { message: error instanceof Error ? error.message : String(error) });
5370
5467
  }
5371
5468
  }
5372
5469
  function setGitHubStateCookie(c, deps, state) {
@@ -5380,7 +5477,7 @@ function setGitHubStateCookie(c, deps, state) {
5380
5477
  }
5381
5478
  function requireGitHubStateCookie(c, state) {
5382
5479
  if (getCookie(c, githubStateCookie) !== state) {
5383
- throw new HTTPException13(400, { message: "invalid or expired GitHub installation browser state" });
5480
+ throw new HTTPException15(400, { message: "invalid or expired GitHub installation browser state" });
5384
5481
  }
5385
5482
  }
5386
5483
  function isSecureRequest(c, deps) {
@@ -5447,7 +5544,7 @@ import {
5447
5544
  updatePackInstallationStatus
5448
5545
  } from "@opengeni/db";
5449
5546
  import { getDocumentBase as getDocumentBase2 } from "@opengeni/documents";
5450
- import { HTTPException as HTTPException14 } from "hono/http-exception";
5547
+ import { HTTPException as HTTPException16 } from "hono/http-exception";
5451
5548
  import { requireAccessGrant as requireAccessGrant11 } from "@opengeni/core";
5452
5549
  import { requireLimit as requireLimit5 } from "@opengeni/core";
5453
5550
  import { validateEnvironmentAttachment } from "@opengeni/core";
@@ -5478,7 +5575,7 @@ function registerPackRoutes(app, deps) {
5478
5575
  const grant = await requireAccessGrant11(c, deps, workspaceId, "workspace:admin");
5479
5576
  const manifest = RegisterCapabilityPackRequest.parse(await c.req.json());
5480
5577
  if (isBuiltInCapabilityPack(manifest.id)) {
5481
- throw new HTTPException14(409, { message: `pack id ${manifest.id} is a built-in pack and cannot be replaced` });
5578
+ throw new HTTPException16(409, { message: `pack id ${manifest.id} is a built-in pack and cannot be replaced` });
5482
5579
  }
5483
5580
  const { pack, created } = await registerWorkspacePack(db, {
5484
5581
  accountId: grant.accountId,
@@ -5492,10 +5589,10 @@ function registerPackRoutes(app, deps) {
5492
5589
  await requireAccessGrant11(c, deps, workspaceId, "workspace:admin");
5493
5590
  const packId = c.req.param("packId");
5494
5591
  if (isBuiltInCapabilityPack(packId)) {
5495
- throw new HTTPException14(409, { message: "built-in packs cannot be unregistered" });
5592
+ throw new HTTPException16(409, { message: "built-in packs cannot be unregistered" });
5496
5593
  }
5497
5594
  if (!await getWorkspacePack(db, workspaceId, packId)) {
5498
- throw new HTTPException14(404, { message: "pack not found" });
5595
+ throw new HTTPException16(404, { message: "pack not found" });
5499
5596
  }
5500
5597
  const installation = await getPackInstallation(db, workspaceId, packId);
5501
5598
  if (installation && installation.status === "active") {
@@ -5532,13 +5629,13 @@ function registerPackRoutes(app, deps) {
5532
5629
  const storedEnvironmentId = typeof existing?.metadata.environmentId === "string" ? existing.metadata.environmentId : void 0;
5533
5630
  const environmentId = payload.environmentId ?? storedEnvironmentId;
5534
5631
  if (pack.environment?.required && !environmentId) {
5535
- throw new HTTPException14(422, { message: "this pack requires an environment attachment; pass environmentId" });
5632
+ throw new HTTPException16(422, { message: "this pack requires an environment attachment; pass environmentId" });
5536
5633
  }
5537
5634
  if (environmentId) {
5538
5635
  const environment = await validateEnvironmentAttachment({ settings, db }, grant, workspaceId, environmentId, { preauthorized: !payload.environmentId });
5539
5636
  const missing = (pack.environment?.requiredVariables ?? []).filter((name) => !environment.variables.some((variable) => variable.name === name));
5540
5637
  if (missing.length > 0) {
5541
- throw new HTTPException14(422, { message: `environment is missing required variable(s): ${missing.join(", ")}` });
5638
+ throw new HTTPException16(422, { message: `environment is missing required variable(s): ${missing.join(", ")}` });
5542
5639
  }
5543
5640
  }
5544
5641
  const installation = await enablePackInstallation(db, {
@@ -5559,13 +5656,13 @@ function registerPackRoutes(app, deps) {
5559
5656
  const pack = await requirePack(db, workspaceId, MARKETING_SOCIAL_PACK_ID);
5560
5657
  const installation = await getPackInstallation(db, workspaceId, pack.id);
5561
5658
  if (installation?.status !== "active") {
5562
- throw new HTTPException14(409, { message: "enable the marketing social pack before creating its scheduled tasks" });
5659
+ throw new HTTPException16(409, { message: "enable the marketing social pack before creating its scheduled tasks" });
5563
5660
  }
5564
5661
  const payload = MarketingDailyAnalysisTaskRequest.parse(await c.req.json());
5565
5662
  await requireLimit5(deps, { accountId: grant.accountId, workspaceId, action: "schedule:create", quantity: 1 });
5566
5663
  const connections = await resolveSocialConnections(db, workspaceId, payload.connectionIds);
5567
5664
  if (connections.length === 0) {
5568
- throw new HTTPException14(422, { message: "at least one connected social account is required" });
5665
+ throw new HTTPException16(422, { message: "at least one connected social account is required" });
5569
5666
  }
5570
5667
  await validateDocumentBaseIds(db, workspaceId, payload.documentBaseIds);
5571
5668
  const agentConfig = buildMarketingDailyAnalysisAgentConfig({
@@ -5609,7 +5706,7 @@ function registerPackRoutes(app, deps) {
5609
5706
  async function requirePack(db, workspaceId, packId) {
5610
5707
  const pack = await resolveCapabilityPack(db, workspaceId, packId);
5611
5708
  if (!pack) {
5612
- throw new HTTPException14(404, { message: "pack not found" });
5709
+ throw new HTTPException16(404, { message: "pack not found" });
5613
5710
  }
5614
5711
  return pack;
5615
5712
  }
@@ -5618,13 +5715,13 @@ async function resolveSocialConnections(db, workspaceId, connectionIds) {
5618
5715
  const connections = ids.length > 0 ? await Promise.all(ids.map(async (id) => {
5619
5716
  const connection = await getSocialConnection(db, workspaceId, id);
5620
5717
  if (!connection) {
5621
- throw new HTTPException14(422, { message: `unknown social connection: ${id}` });
5718
+ throw new HTTPException16(422, { message: `unknown social connection: ${id}` });
5622
5719
  }
5623
5720
  return connection;
5624
5721
  })) : (await listSocialConnections2(db, workspaceId, 500)).filter((connection) => connection.status === "connected");
5625
5722
  const inactive = connections.find((connection) => connection.status !== "connected");
5626
5723
  if (inactive) {
5627
- throw new HTTPException14(422, { message: `social connection ${inactive.id} is ${inactive.status}` });
5724
+ throw new HTTPException16(422, { message: `social connection ${inactive.id} is ${inactive.status}` });
5628
5725
  }
5629
5726
  return connections;
5630
5727
  }
@@ -5632,7 +5729,7 @@ async function validateDocumentBaseIds(db, workspaceId, documentBaseIds) {
5632
5729
  for (const baseId of [...new Set(documentBaseIds)]) {
5633
5730
  const base = await getDocumentBase2(db, workspaceId, baseId);
5634
5731
  if (!base) {
5635
- throw new HTTPException14(422, { message: `unknown document base: ${baseId}` });
5732
+ throw new HTTPException16(422, { message: `unknown document base: ${baseId}` });
5636
5733
  }
5637
5734
  }
5638
5735
  }
@@ -5816,7 +5913,7 @@ import {
5816
5913
  releaseLeaseHolder
5817
5914
  } from "@opengeni/db";
5818
5915
  import { appendAndPublishEvents as appendAndPublishEvents3 } from "@opengeni/events";
5819
- import { HTTPException as HTTPException15 } from "hono/http-exception";
5916
+ import { HTTPException as HTTPException17 } from "hono/http-exception";
5820
5917
  import {
5821
5918
  establishSandboxSessionFromEnvelope,
5822
5919
  serializeEstablishedSandboxEnvelope,
@@ -5831,7 +5928,7 @@ async function withChannelA(services, ctx, fn) {
5831
5928
  const { db, settings, bus } = services;
5832
5929
  const { accountId, workspaceId, session, subjectId } = ctx;
5833
5930
  if (session.sandboxBackend === "none") {
5834
- throw new HTTPException15(409, { message: "sandbox not available" });
5931
+ throw new HTTPException17(409, { message: "sandbox not available" });
5835
5932
  }
5836
5933
  const sandboxGroupId = session.sandboxGroupId;
5837
5934
  const viewerId = crypto.randomUUID();
@@ -5859,7 +5956,7 @@ async function withChannelA(services, ctx, fn) {
5859
5956
  });
5860
5957
  if (acquired.role === "fenced") {
5861
5958
  await release();
5862
- throw new HTTPException15(409, { message: `sandbox lease superseded (epoch ${acquired.lease.leaseEpoch}); retry` });
5959
+ throw new HTTPException17(409, { message: `sandbox lease superseded (epoch ${acquired.lease.leaseEpoch}); retry` });
5863
5960
  }
5864
5961
  let established;
5865
5962
  let leaseSnapshot = acquired.lease;
@@ -5882,7 +5979,7 @@ async function withChannelA(services, ctx, fn) {
5882
5979
  });
5883
5980
  } catch (error) {
5884
5981
  await failWarmingToCold(db, { accountId, workspaceId, sandboxGroupId, expectedEpoch });
5885
- throw new HTTPException15(409, { message: `sandbox not available (${error instanceof Error ? error.message : "spawn failed"})` });
5982
+ throw new HTTPException17(409, { message: `sandbox not available (${error instanceof Error ? error.message : "spawn failed"})` });
5886
5983
  }
5887
5984
  const resumeEnvelope = await serializeEstablishedSandboxEnvelope(established) ?? envelope ?? null;
5888
5985
  const committed = await commitWarmingToWarm(db, {
@@ -5897,7 +5994,7 @@ async function withChannelA(services, ctx, fn) {
5897
5994
  leaseTtlMs
5898
5995
  });
5899
5996
  if (!committed.committed || !committed.lease) {
5900
- throw new HTTPException15(409, { message: `sandbox lease superseded (epoch ${expectedEpoch}); retry` });
5997
+ throw new HTTPException17(409, { message: `sandbox lease superseded (epoch ${expectedEpoch}); retry` });
5901
5998
  }
5902
5999
  leaseSnapshot = committed.lease;
5903
6000
  } else {
@@ -5936,11 +6033,11 @@ async function withChannelA(services, ctx, fn) {
5936
6033
  }
5937
6034
  }
5938
6035
  function mapChannelAError(error) {
5939
- if (error instanceof HTTPException15) return error;
5940
- if (error instanceof ChannelAValidationError) return new HTTPException15(400, { message: error.message });
5941
- if (error instanceof ChannelANotFoundError) return new HTTPException15(404, { message: error.message });
5942
- if (error instanceof ChannelAConflictError) return new HTTPException15(409, { message: error.message });
5943
- if (error instanceof ChannelAUnsupportedError) return new HTTPException15(409, { message: error.message });
6036
+ if (error instanceof HTTPException17) return error;
6037
+ if (error instanceof ChannelAValidationError) return new HTTPException17(400, { message: error.message });
6038
+ if (error instanceof ChannelANotFoundError) return new HTTPException17(404, { message: error.message });
6039
+ if (error instanceof ChannelAConflictError) return new HTTPException17(409, { message: error.message });
6040
+ if (error instanceof ChannelAUnsupportedError) return new HTTPException17(409, { message: error.message });
5944
6041
  return error;
5945
6042
  }
5946
6043
  async function dropEstablishedHandle(established) {
@@ -5949,7 +6046,7 @@ async function dropEstablishedHandle(established) {
5949
6046
 
5950
6047
  // src/routes/sessions.ts
5951
6048
  import { negotiateCapabilities } from "@opengeni/runtime/sandbox";
5952
- import { HTTPException as HTTPException17 } from "hono/http-exception";
6049
+ import { HTTPException as HTTPException19 } from "hono/http-exception";
5953
6050
  import { requireAccessGrant as requireAccessGrant13 } from "@opengeni/core";
5954
6051
 
5955
6052
  // src/sandbox/viewer.ts
@@ -5971,7 +6068,7 @@ import {
5971
6068
  SandboxLeaseSupersededError as SandboxLeaseSupersededError2
5972
6069
  } from "@opengeni/db";
5973
6070
  import { appendAndPublishEvents as appendAndPublishEvents4 } from "@opengeni/events";
5974
- import { HTTPException as HTTPException16 } from "hono/http-exception";
6071
+ import { HTTPException as HTTPException18 } from "hono/http-exception";
5975
6072
  import {
5976
6073
  DESKTOP_STREAM_PORT,
5977
6074
  ensureDisplayStack,
@@ -6033,7 +6130,7 @@ async function attachViewer(services, input) {
6033
6130
  });
6034
6131
  if (acquired.role === "fenced") {
6035
6132
  await release();
6036
- throw new HTTPException16(409, { message: `sandbox lease superseded (epoch ${acquired.lease.leaseEpoch}); re-read capabilities and re-attach` });
6133
+ throw new HTTPException18(409, { message: `sandbox lease superseded (epoch ${acquired.lease.leaseEpoch}); re-read capabilities and re-attach` });
6037
6134
  }
6038
6135
  if (acquired.role === "spawner") {
6039
6136
  const expectedEpoch = acquired.lease.leaseEpoch;
@@ -6074,12 +6171,12 @@ async function attachViewer(services, input) {
6074
6171
  };
6075
6172
  } catch (error) {
6076
6173
  if (error instanceof SandboxLeaseSupersededError2) {
6077
- throw new HTTPException16(409, { message: `sandbox lease superseded (epoch ${error.leaseEpoch}); re-read capabilities and re-attach` });
6174
+ throw new HTTPException18(409, { message: `sandbox lease superseded (epoch ${error.leaseEpoch}); re-read capabilities and re-attach` });
6078
6175
  }
6079
6176
  await failWarmingToCold2(db, { accountId, workspaceId, sandboxGroupId, expectedEpoch });
6080
6177
  await release();
6081
- if (error instanceof HTTPException16) throw error;
6082
- throw new HTTPException16(409, { message: `sandbox not available (${error instanceof Error ? error.message : "spawn failed"})` });
6178
+ if (error instanceof HTTPException18) throw error;
6179
+ throw new HTTPException18(409, { message: `sandbox not available (${error instanceof Error ? error.message : "spawn failed"})` });
6083
6180
  } finally {
6084
6181
  await dropEstablishedHandle2(established);
6085
6182
  }
@@ -6546,7 +6643,7 @@ function registerSessionRoutes(app, deps) {
6546
6643
  await requireAccessGrant13(c, deps, workspaceId, "sessions:read");
6547
6644
  const session = await getSession4(db, workspaceId, c.req.param("sessionId"));
6548
6645
  if (!session) {
6549
- throw new HTTPException17(404, { message: "session not found" });
6646
+ throw new HTTPException19(404, { message: "session not found" });
6550
6647
  }
6551
6648
  return c.json(session);
6552
6649
  });
@@ -6557,12 +6654,12 @@ function registerSessionRoutes(app, deps) {
6557
6654
  const body = await c.req.json();
6558
6655
  const target = typeof body.target === "string" ? body.target : "";
6559
6656
  if (!target) {
6560
- throw new HTTPException17(400, { message: 'target is required ("auto" or an account id)' });
6657
+ throw new HTTPException19(400, { message: 'target is required ("auto" or an account id)' });
6561
6658
  }
6562
6659
  const pinned = target === "auto" ? null : target;
6563
6660
  const ok = await setSessionCodexPin(db, workspaceId, sessionId, pinned);
6564
6661
  if (!ok) {
6565
- throw new HTTPException17(404, { message: "session or codex account not found" });
6662
+ throw new HTTPException19(404, { message: "session or codex account not found" });
6566
6663
  }
6567
6664
  return c.json({ pinned: target === "auto" ? "auto" : target });
6568
6665
  });
@@ -6575,7 +6672,7 @@ function registerSessionRoutes(app, deps) {
6575
6672
  await updateSessionTitle2({ db, bus }, workspaceId, sessionId, payload.title, "user");
6576
6673
  const session = await getSession4(db, workspaceId, sessionId);
6577
6674
  if (!session) {
6578
- throw new HTTPException17(404, { message: "session not found" });
6675
+ throw new HTTPException19(404, { message: "session not found" });
6579
6676
  }
6580
6677
  return c.json(session);
6581
6678
  });
@@ -6586,7 +6683,7 @@ function registerSessionRoutes(app, deps) {
6586
6683
  await assertSessionExists(db, workspaceId, sessionId);
6587
6684
  const goal = await getSessionGoal2(db, workspaceId, sessionId);
6588
6685
  if (!goal) {
6589
- throw new HTTPException17(404, { message: "session goal not found" });
6686
+ throw new HTTPException19(404, { message: "session goal not found" });
6590
6687
  }
6591
6688
  return c.json(goal);
6592
6689
  });
@@ -6598,10 +6695,10 @@ function registerSessionRoutes(app, deps) {
6598
6695
  const payload = UpdateSessionGoalRequest.parse(await c.req.json());
6599
6696
  const existing = await getSessionGoal2(db, workspaceId, sessionId);
6600
6697
  if (!existing) {
6601
- throw new HTTPException17(404, { message: "session goal not found" });
6698
+ throw new HTTPException19(404, { message: "session goal not found" });
6602
6699
  }
6603
6700
  if (existing.status === "completed") {
6604
- throw new HTTPException17(409, { message: "session goal is completed; set a new goal instead" });
6701
+ throw new HTTPException19(409, { message: "session goal is completed; set a new goal instead" });
6605
6702
  }
6606
6703
  if (payload.status === "paused") {
6607
6704
  const { goal: goal2, changed: changed2 } = await setSessionGoalStatus2(db, workspaceId, sessionId, {
@@ -6625,7 +6722,7 @@ function registerSessionRoutes(app, deps) {
6625
6722
  return c.json(goal2);
6626
6723
  }
6627
6724
  if (existing.status !== "paused") {
6628
- throw new HTTPException17(409, { message: `session goal is ${existing.status}; only paused goals can be resumed` });
6725
+ throw new HTTPException19(409, { message: `session goal is ${existing.status}; only paused goals can be resumed` });
6629
6726
  }
6630
6727
  const { goal, changed } = await setSessionGoalStatus2(db, workspaceId, sessionId, { status: "active" });
6631
6728
  if (changed) {
@@ -6650,11 +6747,11 @@ function registerSessionRoutes(app, deps) {
6650
6747
  await assertSessionExists(db, workspaceId, sessionId);
6651
6748
  const clearBody = ClearSessionContextRequest.safeParse(await c.req.json().catch(() => ({})));
6652
6749
  if (!clearBody.success) {
6653
- throw new HTTPException17(400, { message: "context clear requires an explicit { confirm: true }" });
6750
+ throw new HTTPException19(400, { message: "context clear requires an explicit { confirm: true }" });
6654
6751
  }
6655
6752
  const session = await requireSession3(db, workspaceId, sessionId);
6656
6753
  if (session.status === "queued" || session.status === "running" || session.status === "requires_action") {
6657
- throw new HTTPException17(409, { message: `session is ${session.status}; cannot clear context mid-turn \u2014 stop the turn first` });
6754
+ throw new HTTPException19(409, { message: `session is ${session.status}; cannot clear context mid-turn \u2014 stop the turn first` });
6658
6755
  }
6659
6756
  const result = await clearSessionContext(db, { accountId: grant.accountId, workspaceId, sessionId });
6660
6757
  await appendAndPublishEvents5(db, bus, workspaceId, sessionId, [{
@@ -6731,7 +6828,7 @@ function registerSessionRoutes(app, deps) {
6731
6828
  const resources = payload.resources !== void 0 ? normalizeResources(payload.resources) : existing.resources;
6732
6829
  const tools = payload.tools !== void 0 ? validateToolRefs(payload.tools, runtimeSettings) : existing.tools;
6733
6830
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
6734
- throw new HTTPException17(503, { message: "object storage is not configured" });
6831
+ throw new HTTPException19(503, { message: "object storage is not configured" });
6735
6832
  }
6736
6833
  await validateFileResources(db, workspaceId, resources);
6737
6834
  await validateGitHubRepositorySelection(db, workspaceId, [...session.resources, ...resources]);
@@ -6801,7 +6898,7 @@ function registerSessionRoutes(app, deps) {
6801
6898
  }
6802
6899
  const session = await requireSession3(db, workspaceId, sessionId);
6803
6900
  if (event.type === "user.approvalDecision" && session.status !== "requires_action") {
6804
- throw new HTTPException17(409, { message: `session is ${session.status}; no approval is pending` });
6901
+ throw new HTTPException19(409, { message: `session is ${session.status}; no approval is pending` });
6805
6902
  }
6806
6903
  const eventsToAppend = [{
6807
6904
  type: event.type,
@@ -6811,7 +6908,7 @@ function registerSessionRoutes(app, deps) {
6811
6908
  const appended = await appendAndPublishEvents5(db, bus, workspaceId, sessionId, eventsToAppend);
6812
6909
  const accepted = appended[0];
6813
6910
  if (!accepted) {
6814
- throw new HTTPException17(500, { message: "failed to append client event" });
6911
+ throw new HTTPException19(500, { message: "failed to append client event" });
6815
6912
  }
6816
6913
  const workflowId = workflowIdForSession2(sessionId);
6817
6914
  if (event.type === "user.approvalDecision") {
@@ -6829,7 +6926,7 @@ function registerSessionRoutes(app, deps) {
6829
6926
  });
6830
6927
  function assertOwnershipEnabled() {
6831
6928
  if (!settings.sandboxOwnershipEnabled) {
6832
- throw new HTTPException17(404, { message: "sandbox ownership is not enabled for this deployment" });
6929
+ throw new HTTPException19(404, { message: "sandbox ownership is not enabled for this deployment" });
6833
6930
  }
6834
6931
  }
6835
6932
  async function resolveSharedExposure(workspaceId, session) {
@@ -6844,7 +6941,7 @@ function registerSessionRoutes(app, deps) {
6844
6941
  const sessionId = c.req.param("sessionId");
6845
6942
  const session = await getSession4(db, workspaceId, sessionId);
6846
6943
  if (!session) {
6847
- throw new HTTPException17(404, { message: "session not found" });
6944
+ throw new HTTPException19(404, { message: "session not found" });
6848
6945
  }
6849
6946
  const lease = await readGroupLease({ db, settings }, { workspaceId, sandboxGroupId: session.sandboxGroupId });
6850
6947
  const { shared, sharedSessionIds } = await resolveSharedExposure(workspaceId, session);
@@ -6939,11 +7036,11 @@ function registerSessionRoutes(app, deps) {
6939
7036
  const sessionId = c.req.param("sessionId");
6940
7037
  const session = await getSession4(db, workspaceId, sessionId);
6941
7038
  if (!session) {
6942
- throw new HTTPException17(404, { message: "session not found" });
7039
+ throw new HTTPException19(404, { message: "session not found" });
6943
7040
  }
6944
7041
  const parsed = AcknowledgeStreamRequest.safeParse(await c.req.json().catch(() => ({})));
6945
7042
  if (!parsed.success) {
6946
- throw new HTTPException17(400, { message: "invalid stream acknowledgment request" });
7043
+ throw new HTTPException19(400, { message: "invalid stream acknowledgment request" });
6947
7044
  }
6948
7045
  const recorded = await recordStreamAcknowledgment(db, {
6949
7046
  accountId: grant.accountId,
@@ -6962,21 +7059,21 @@ function registerSessionRoutes(app, deps) {
6962
7059
  const sessionId = c.req.param("sessionId");
6963
7060
  const session = await getSession4(db, workspaceId, sessionId);
6964
7061
  if (!session) {
6965
- throw new HTTPException17(404, { message: "session not found" });
7062
+ throw new HTTPException19(404, { message: "session not found" });
6966
7063
  }
6967
7064
  const parsed = AttachViewerRequest.safeParse(await c.req.json().catch(() => ({})));
6968
7065
  if (!parsed.success) {
6969
- throw new HTTPException17(400, { message: "invalid viewer attach request" });
7066
+ throw new HTTPException19(400, { message: "invalid viewer attach request" });
6970
7067
  }
6971
7068
  const wantDesktop = parsed.data.desktop ?? false;
6972
7069
  const { shared } = await resolveSharedExposure(workspaceId, session);
6973
7070
  if (wantDesktop) {
6974
7071
  const ack = await getStreamAcknowledgment(db, { workspaceId, sandboxGroupId: session.sandboxGroupId, subjectId: grant.subjectId });
6975
7072
  if (!ack?.acknowledgedUnredacted) {
6976
- throw new HTTPException17(409, { message: "stream_acknowledgment_required" });
7073
+ throw new HTTPException19(409, { message: "stream_acknowledgment_required" });
6977
7074
  }
6978
7075
  if (shared && !ack.acknowledgedShared) {
6979
- throw new HTTPException17(409, { message: "shared_acknowledgment_required" });
7076
+ throw new HTTPException19(409, { message: "shared_acknowledgment_required" });
6980
7077
  }
6981
7078
  }
6982
7079
  const activeSandbox = session.activeSandboxId ? await getSandbox2(db, workspaceId, session.activeSandboxId) : null;
@@ -7075,11 +7172,11 @@ function registerSessionRoutes(app, deps) {
7075
7172
  const sessionId = c.req.param("sessionId");
7076
7173
  const session = await getSession4(db, workspaceId, sessionId);
7077
7174
  if (!session) {
7078
- throw new HTTPException17(404, { message: "session not found" });
7175
+ throw new HTTPException19(404, { message: "session not found" });
7079
7176
  }
7080
7177
  const parsed = ViewerHeartbeatRequest.safeParse(await c.req.json().catch(() => ({})));
7081
7178
  if (!parsed.success) {
7082
- throw new HTTPException17(400, { message: "viewer heartbeat requires { leaseEpoch }" });
7179
+ throw new HTTPException19(400, { message: "viewer heartbeat requires { leaseEpoch }" });
7083
7180
  }
7084
7181
  const alive = await heartbeatViewer({ db, settings }, {
7085
7182
  accountId: grant.accountId,
@@ -7097,7 +7194,7 @@ function registerSessionRoutes(app, deps) {
7097
7194
  const sessionId = c.req.param("sessionId");
7098
7195
  const session = await getSession4(db, workspaceId, sessionId);
7099
7196
  if (!session) {
7100
- throw new HTTPException17(404, { message: "session not found" });
7197
+ throw new HTTPException19(404, { message: "session not found" });
7101
7198
  }
7102
7199
  await detachViewer({ db, settings }, {
7103
7200
  accountId: grant.accountId,
@@ -7114,7 +7211,7 @@ function registerSessionRoutes(app, deps) {
7114
7211
  const sessionId = c.req.param("sessionId");
7115
7212
  const session = await getSession4(db, workspaceId, sessionId);
7116
7213
  if (!session) {
7117
- throw new HTTPException17(404, { message: "session not found" });
7214
+ throw new HTTPException19(404, { message: "session not found" });
7118
7215
  }
7119
7216
  const result = await revokeViewer(db, {
7120
7217
  accountId: grant.accountId,
@@ -7132,7 +7229,7 @@ function registerSessionRoutes(app, deps) {
7132
7229
  const sessionId = c.req.param("sessionId") ?? "";
7133
7230
  const session = await getSession4(db, workspaceId, sessionId);
7134
7231
  if (!session) {
7135
- throw new HTTPException17(404, { message: "session not found" });
7232
+ throw new HTTPException19(404, { message: "session not found" });
7136
7233
  }
7137
7234
  return { accountId: grant.accountId, workspaceId, session, subjectId: grant.subjectId };
7138
7235
  }
@@ -7140,7 +7237,7 @@ function registerSessionRoutes(app, deps) {
7140
7237
  const raw = await c.req.json().catch(() => void 0);
7141
7238
  const result = schema.safeParse(raw ?? {});
7142
7239
  if (!result.success) {
7143
- throw new HTTPException17(400, { message: "invalid request body" });
7240
+ throw new HTTPException19(400, { message: "invalid request body" });
7144
7241
  }
7145
7242
  return result.data;
7146
7243
  }
@@ -7245,10 +7342,10 @@ function registerSessionRoutes(app, deps) {
7245
7342
  const req = await parseChannelABody(c, PtyWriteRequest);
7246
7343
  const pty = await getOpenPtySession(db, ctx.workspaceId, req.ptyId);
7247
7344
  if (!pty) {
7248
- throw new HTTPException17(404, { message: "pty not found or closed" });
7345
+ throw new HTTPException19(404, { message: "pty not found or closed" });
7249
7346
  }
7250
7347
  if (pty.execSessionId === null) {
7251
- throw new HTTPException17(409, { message: "interactive terminal unsupported on this backend" });
7348
+ throw new HTTPException19(409, { message: "interactive terminal unsupported on this backend" });
7252
7349
  }
7253
7350
  let seq = 1;
7254
7351
  await withChannelA({ db, settings, bus }, ctx, async ({ service }) => {
@@ -7266,7 +7363,7 @@ function registerSessionRoutes(app, deps) {
7266
7363
  const req = await parseChannelABody(c, PtyResizeRequest);
7267
7364
  const pty = await getOpenPtySession(db, ctx.workspaceId, req.ptyId);
7268
7365
  if (!pty) {
7269
- throw new HTTPException17(404, { message: "pty not found or closed" });
7366
+ throw new HTTPException19(404, { message: "pty not found or closed" });
7270
7367
  }
7271
7368
  if (pty.execSessionId !== null) {
7272
7369
  await withChannelA({ db, settings, bus }, ctx, ({ service }) => service.ptyResize(req, pty.execSessionId));
@@ -7336,7 +7433,7 @@ import {
7336
7433
  listSocialConnections as listSocialConnections3,
7337
7434
  listSocialPosts as listSocialPosts2
7338
7435
  } from "@opengeni/db";
7339
- import { HTTPException as HTTPException18 } from "hono/http-exception";
7436
+ import { HTTPException as HTTPException20 } from "hono/http-exception";
7340
7437
  import { z as z2 } from "zod";
7341
7438
  import { requireAccessGrant as requireAccessGrant14 } from "@opengeni/core";
7342
7439
  function registerSocialRoutes(app, deps) {
@@ -7408,7 +7505,7 @@ function parseSince(raw) {
7408
7505
  }
7409
7506
  const since = new Date(raw);
7410
7507
  if (Number.isNaN(since.getTime())) {
7411
- throw new HTTPException18(422, { message: "since must be an ISO date-time" });
7508
+ throw new HTTPException20(422, { message: "since must be an ISO date-time" });
7412
7509
  }
7413
7510
  return since;
7414
7511
  }
@@ -7419,7 +7516,7 @@ function parseConnectionIds(raw) {
7419
7516
  const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
7420
7517
  const parsed = z2.array(z2.string().uuid()).safeParse(values);
7421
7518
  if (!parsed.success) {
7422
- throw new HTTPException18(422, { message: "connectionIds must be a comma-separated list of UUIDs" });
7519
+ throw new HTTPException20(422, { message: "connectionIds must be a comma-separated list of UUIDs" });
7423
7520
  }
7424
7521
  const ids = parsed.data;
7425
7522
  return [...new Set(ids)];
@@ -7427,12 +7524,12 @@ function parseConnectionIds(raw) {
7427
7524
  function socialHttpException(error) {
7428
7525
  const message = error instanceof Error ? error.message : String(error);
7429
7526
  if (message.includes("not found")) {
7430
- return new HTTPException18(404, { message });
7527
+ return new HTTPException20(404, { message });
7431
7528
  }
7432
7529
  if (message.includes("duplicate key")) {
7433
- return new HTTPException18(409, { message: "social connection or post already exists" });
7530
+ return new HTTPException20(409, { message: "social connection or post already exists" });
7434
7531
  }
7435
- return new HTTPException18(500, { message });
7532
+ return new HTTPException20(500, { message });
7436
7533
  }
7437
7534
 
7438
7535
  // src/routes/workspaces.ts
@@ -7460,7 +7557,7 @@ import {
7460
7557
  requireWorkspace,
7461
7558
  updateWorkspace
7462
7559
  } from "@opengeni/db";
7463
- import { HTTPException as HTTPException19 } from "hono/http-exception";
7560
+ import { HTTPException as HTTPException21 } from "hono/http-exception";
7464
7561
  import { hasPermission as hasPermission3, requireAccessContext as requireAccessContext2, requireAccessGrant as requireAccessGrant15 } from "@opengeni/core";
7465
7562
  import { requireLimit as requireLimit7 } from "@opengeni/core";
7466
7563
  import { assertWorkspaceDeletable, assertWorkspaceMemberRemovable, resolveMemberSubjectId } from "@opengeni/core";
@@ -7482,7 +7579,7 @@ function registerWorkspaceRoutes(app, deps) {
7482
7579
  const payload = CreateWorkspaceRequest.parse(await c.req.json());
7483
7580
  const accountId = payload.accountId ?? context.defaultAccountId;
7484
7581
  if (!accountId) {
7485
- throw new HTTPException19(409, { message: "account selection is required" });
7582
+ throw new HTTPException21(409, { message: "account selection is required" });
7486
7583
  }
7487
7584
  requireAccountPermission(context, accountId, "workspace:create");
7488
7585
  await requireLimit7(deps, { accountId, action: "workspace:create", quantity: 1 });
@@ -7558,7 +7655,7 @@ function registerWorkspaceRoutes(app, deps) {
7558
7655
  const members = await listWorkspaceMembers(deps.db, workspaceId);
7559
7656
  const member = members.find((candidate) => candidate.subjectId === subjectId);
7560
7657
  if (!member) {
7561
- throw new HTTPException19(500, { message: "failed to add member" });
7658
+ throw new HTTPException21(500, { message: "failed to add member" });
7562
7659
  }
7563
7660
  return c.json(WorkspaceMember.parse(member), 201);
7564
7661
  });
@@ -7570,7 +7667,7 @@ function registerWorkspaceRoutes(app, deps) {
7570
7667
  const existing = await listWorkspaceMembers(deps.db, workspaceId);
7571
7668
  const current = existing.find((member2) => member2.subjectId === subjectId);
7572
7669
  if (!current) {
7573
- throw new HTTPException19(404, { message: "member not found" });
7670
+ throw new HTTPException21(404, { message: "member not found" });
7574
7671
  }
7575
7672
  await grantWorkspaceAccess(deps.db, {
7576
7673
  accountId: grant.accountId,
@@ -7583,7 +7680,7 @@ function registerWorkspaceRoutes(app, deps) {
7583
7680
  const members = await listWorkspaceMembers(deps.db, workspaceId);
7584
7681
  const member = members.find((candidate) => candidate.subjectId === subjectId);
7585
7682
  if (!member) {
7586
- throw new HTTPException19(500, { message: "failed to update member" });
7683
+ throw new HTTPException21(500, { message: "failed to update member" });
7587
7684
  }
7588
7685
  return c.json(WorkspaceMember.parse(member));
7589
7686
  });
@@ -7607,7 +7704,7 @@ function normalizeAgentInstructions(value) {
7607
7704
  function requireAccountPermission(context, accountId, permission) {
7608
7705
  const grant = context.accountGrants.find((candidate) => candidate.accountId === accountId);
7609
7706
  if (!grant || !grant.permissions.includes(permission) && !grant.permissions.includes("account:admin")) {
7610
- throw new HTTPException19(403, { message: `missing permission: ${permission}` });
7707
+ throw new HTTPException21(403, { message: `missing permission: ${permission}` });
7611
7708
  }
7612
7709
  }
7613
7710
 
@@ -7634,7 +7731,7 @@ function createApp(deps) {
7634
7731
  const documentIndexer = deps.documentIndexer ?? {
7635
7732
  indexDocument: async ({ accountId, workspaceId, documentId }) => {
7636
7733
  if (!objectStorage) {
7637
- throw new HTTPException20(503, { message: "object storage is not configured" });
7734
+ throw new HTTPException22(503, { message: "object storage is not configured" });
7638
7735
  }
7639
7736
  return await indexDocumentNow(deps.db, objectStorage, workspaceId, documentId, getDocumentServices(), {
7640
7737
  beforeEmbed: async ({ chunkCount }) => {
@@ -7795,6 +7892,7 @@ function createApp(deps) {
7795
7892
  registerSocialRoutes(app, routeDeps);
7796
7893
  registerConnectionRoutes(app, routeDeps);
7797
7894
  registerCapabilityRoutes(app, routeDeps);
7895
+ registerCatalogAssetRoutes(app, routeDeps);
7798
7896
  registerEnrollmentRoutes(app, routeDeps);
7799
7897
  registerMachineRoutes(app, routeDeps);
7800
7898
  registerEnvironmentRoutes(app, routeDeps);
@@ -7835,7 +7933,7 @@ function allowedCorsOrigin(pattern, origin) {
7835
7933
  return new RegExp(`^(?:${pattern})$`).test(origin);
7836
7934
  }
7837
7935
  function httpStatusForError(error) {
7838
- if (error instanceof HTTPException20) {
7936
+ if (error instanceof HTTPException22) {
7839
7937
  return error.status;
7840
7938
  }
7841
7939
  return 500;
@@ -7958,6 +8056,7 @@ var routeLabelPatterns = [
7958
8056
  { pattern: /^\/v1\/workspaces\/[^/]+\/connections$/, label: "/v1/workspaces/:workspaceId/connections" },
7959
8057
  { pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/, label: "/v1/workspaces/:workspaceId/connections/oauth/start" },
7960
8058
  { pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/, label: "/v1/workspaces/:workspaceId/connections/:connectionId" },
8059
+ { pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
7961
8060
  { pattern: /^\/v1\/integrations\/oauth\/callback$/, label: "/v1/integrations/oauth/callback" },
7962
8061
  { pattern: /^\/v1\/integrations\/oauth\/client-metadata\.json$/, label: "/v1/integrations/oauth/client-metadata.json" },
7963
8062
  { pattern: /^\/v1\/enrollments\/device\/start$/, label: "/v1/enrollments/device/start" },
@@ -7997,4 +8096,4 @@ export {
7997
8096
  withDefaultEnabledCapabilityMcpTools,
7998
8097
  workflowIdForSession3 as workflowIdForSession
7999
8098
  };
8000
- //# sourceMappingURL=chunk-DQ5TIRDZ.js.map
8099
+ //# sourceMappingURL=chunk-YY6OAEL6.js.map