@opengeni/api-router 0.17.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,9 +17,10 @@ import {
17
17
  } from "@opengeni/contracts";
18
18
  import {
19
19
  createDocumentServices,
20
+ getDocument as getDocument2,
20
21
  indexDocumentNow
21
22
  } from "@opengeni/documents";
22
- import { dbSql, getWorkspace as getWorkspace5 } from "@opengeni/db";
23
+ import { dbSql, getWorkspace as getWorkspace5, rlsContextForWorkspace } from "@opengeni/db";
23
24
  import { createObservability } from "@opengeni/observability";
24
25
  import { createObjectStorage } from "@opengeni/storage";
25
26
  import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport3 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
@@ -10948,6 +10949,8 @@ import {
10948
10949
  import {
10949
10950
  GOOGLE_DRIVE_PROVIDER_DOMAIN as GOOGLE_DRIVE_PROVIDER_DOMAIN2,
10950
10951
  GoogleDriveConnectionMetadata as GoogleDriveConnectionMetadata2,
10952
+ GoogleDriveDisconnectRequest,
10953
+ GoogleDriveLifecycleActionRequest,
10951
10954
  GoogleDriveOAuthStartRequest,
10952
10955
  GoogleDriveOAuthStartResponse as GoogleDriveOAuthStartResponse2
10953
10956
  } from "@opengeni/contracts/google-drive";
@@ -10985,6 +10988,7 @@ import {
10985
10988
  GOOGLE_DRIVE_READONLY_SCOPE,
10986
10989
  GoogleDriveBrowseItem,
10987
10990
  GoogleDriveBrowseResponse,
10991
+ GoogleDriveConnectionLifecycle,
10988
10992
  GoogleDriveConnectionMetadata,
10989
10993
  GoogleDriveOAuthStartResponse,
10990
10994
  SaveGoogleDriveSourceRequest,
@@ -10994,13 +10998,17 @@ import {
10994
10998
  import { hasPermission as hasPermission7, requireEnvironmentEncryption as requireEnvironmentEncryption3 } from "@opengeni/core";
10995
10999
  import {
10996
11000
  buildConnectionTokenResolver as buildConnectionTokenResolver3,
11001
+ ConnectionDisconnectGenerationError,
11002
+ ConnectionDisconnectIdempotencyError,
10997
11003
  consumeIntegrationOAuthStateNonce as consumeIntegrationOAuthStateNonce3,
10998
11004
  createConnection as createConnection2,
10999
11005
  decryptEnvironmentValue as decryptEnvironmentValue3,
11006
+ disconnectConnectionIdempotently,
11000
11007
  encryptEnvironmentValue as encryptEnvironmentValue4,
11001
11008
  getConnectionMetadata as getConnectionMetadata2,
11002
11009
  getWorkspaceGrant as getWorkspaceGrant4,
11003
11010
  loadConnectionCredentialForBroker,
11011
+ transitionConnectionState,
11004
11012
  updateConnection as updateConnection2
11005
11013
  } from "@opengeni/db";
11006
11014
  import { createSignedState as createSignedState5, readSignedState as readSignedState4 } from "@opengeni/github";
@@ -11014,6 +11022,12 @@ var GOOGLE_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
11014
11022
  var GOOGLE_REQUEST_TIMEOUT_MS = 1e4;
11015
11023
  var GOOGLE_DRIVE_PAGE_SIZE = 100;
11016
11024
  var GOOGLE_DRIVE_RETURN_PATH = (workspaceId) => `/workspaces/${workspaceId}/capabilities`;
11025
+ var GOOGLE_DRIVE_RECONSENT_ERROR_CODES = /* @__PURE__ */ new Set([
11026
+ "appNotAuthorizedToFile",
11027
+ "authError",
11028
+ "insufficientFilePermissions",
11029
+ "insufficientPermissions"
11030
+ ]);
11017
11031
  async function startGoogleDriveOAuth(deps, input) {
11018
11032
  const google = requireGoogleDriveSettings(deps.settings);
11019
11033
  const existing = input.payload.connectionId ? await getConnectionMetadata2(
@@ -11156,6 +11170,7 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
11156
11170
  googleDisplayName: identity.displayName,
11157
11171
  verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
11158
11172
  accessMode: scopeDecision.accessMode,
11173
+ lifecycle: googleDriveLifecycle("active"),
11159
11174
  ...previousMetadata?.selectedSources ? { selectedSources: previousMetadata.selectedSources } : previousMetadata?.selectedSource ? { selectedSources: [previousMetadata.selectedSource] } : {}
11160
11175
  });
11161
11176
  const connection = existing ? await updateConnection2(deps.db, {
@@ -11201,6 +11216,101 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
11201
11216
  };
11202
11217
  }
11203
11218
  }
11219
+ async function transitionGoogleDriveLifecycle(deps, input) {
11220
+ const existing = await getConnectionMetadata2(
11221
+ deps.db,
11222
+ input.workspaceId,
11223
+ input.connectionId,
11224
+ input.subjectId
11225
+ );
11226
+ if (!existing) {
11227
+ throw new HTTPException10(404, { message: "Google Drive connection not found" });
11228
+ }
11229
+ const metadata = requireGoogleDriveConnection(existing, input.subjectId);
11230
+ const lifecycle = effectiveGoogleDriveLifecycle(existing, metadata);
11231
+ const targetState = input.payload.action === "pause" ? "paused" : "active";
11232
+ if (existing.status === "active" && lifecycle.state === targetState) {
11233
+ return existing;
11234
+ }
11235
+ if (existing.status === "revoked") {
11236
+ throw new HTTPException10(409, {
11237
+ message: "Google Drive is disconnected; connect it again instead"
11238
+ });
11239
+ }
11240
+ if (input.payload.action === "pause" && lifecycle.state !== "active") {
11241
+ throw new HTTPException10(409, {
11242
+ message: "Google Drive must be reconnected before it can be paused"
11243
+ });
11244
+ }
11245
+ if (input.payload.action === "resume" && lifecycle.state !== "paused") {
11246
+ throw new HTTPException10(409, {
11247
+ message: "Google Drive must be reconnected or re-consented before it can resume"
11248
+ });
11249
+ }
11250
+ if (existing.status !== "active" || existing.version !== input.payload.expectedVersion) {
11251
+ throw new HTTPException10(409, { message: "Google Drive connection changed; try again" });
11252
+ }
11253
+ const updated = await transitionConnectionState(deps.db, {
11254
+ workspaceId: input.workspaceId,
11255
+ connectionId: existing.id,
11256
+ visibleToSubjectId: input.subjectId,
11257
+ expectedVersion: existing.version,
11258
+ status: "active",
11259
+ metadata: GoogleDriveConnectionMetadata.parse({
11260
+ ...metadata,
11261
+ lifecycle: googleDriveLifecycle(targetState)
11262
+ }),
11263
+ lastError: null,
11264
+ updatedBySubjectId: input.subjectId
11265
+ });
11266
+ if (!updated) {
11267
+ const converged = await getConnectionMetadata2(
11268
+ deps.db,
11269
+ input.workspaceId,
11270
+ input.connectionId,
11271
+ input.subjectId
11272
+ );
11273
+ if (converged?.status === "active") {
11274
+ const convergedMetadata = requireGoogleDriveConnection(converged, input.subjectId);
11275
+ if (effectiveGoogleDriveLifecycle(converged, convergedMetadata).state === targetState) {
11276
+ return converged;
11277
+ }
11278
+ }
11279
+ throw new HTTPException10(409, { message: "Google Drive connection changed; try again" });
11280
+ }
11281
+ return updated;
11282
+ }
11283
+ async function disconnectGoogleDrive(deps, input) {
11284
+ const metadata = requireGoogleDriveConnection(input.connection, input.subjectId);
11285
+ try {
11286
+ return await disconnectConnectionIdempotently(deps.db, {
11287
+ accountId: input.connection.accountId,
11288
+ workspaceId: input.workspaceId,
11289
+ subjectId: input.subjectId,
11290
+ connectionId: input.connection.id,
11291
+ expectedVersion: input.payload.expectedVersion,
11292
+ idempotencyKey: input.payload.idempotencyKey,
11293
+ metadata: GoogleDriveConnectionMetadata.parse({
11294
+ ...metadata,
11295
+ lifecycle: googleDriveLifecycle("disconnected")
11296
+ }),
11297
+ lastError: null,
11298
+ updatedBySubjectId: input.subjectId
11299
+ });
11300
+ } catch (error) {
11301
+ if (error instanceof ConnectionDisconnectIdempotencyError) {
11302
+ throw new HTTPException10(409, {
11303
+ message: "Google Drive disconnect key was already used for another operation"
11304
+ });
11305
+ }
11306
+ if (error instanceof ConnectionDisconnectGenerationError) {
11307
+ throw new HTTPException10(409, {
11308
+ message: "Google Drive connection changed; refresh before disconnecting"
11309
+ });
11310
+ }
11311
+ throw error;
11312
+ }
11313
+ }
11204
11314
  async function browseGoogleDrive(deps, input) {
11205
11315
  const connection = await getConnectionMetadata2(
11206
11316
  deps.db,
@@ -11211,7 +11321,7 @@ async function browseGoogleDrive(deps, input) {
11211
11321
  if (!connection) {
11212
11322
  throw new HTTPException10(404, { message: "Google Drive connection not found" });
11213
11323
  }
11214
- requireGoogleDriveSourceConnection(connection, input.subjectId);
11324
+ await requireGoogleDriveSourceConnection(deps, connection, input.subjectId);
11215
11325
  const parentId = validDriveId(input.parentId, "parentId");
11216
11326
  const currentItem = await resolveGoogleDriveBoundaryItem(deps, {
11217
11327
  workspaceId: input.workspaceId,
@@ -11272,7 +11382,7 @@ async function saveGoogleDriveSource(deps, input) {
11272
11382
  if (!existing) {
11273
11383
  throw new HTTPException10(404, { message: "Google Drive connection not found" });
11274
11384
  }
11275
- requireGoogleDriveSourceConnection(existing, input.subjectId);
11385
+ await requireGoogleDriveSourceConnection(deps, existing, input.subjectId);
11276
11386
  const verifiedSources = [];
11277
11387
  for (const source of payload.sources) {
11278
11388
  const sourceId = validDriveId(source.id, "source.id");
@@ -11295,8 +11405,8 @@ async function saveGoogleDriveSource(deps, input) {
11295
11405
  input.connectionId,
11296
11406
  input.subjectId
11297
11407
  ) ?? existing;
11298
- const latestMetadata = requireGoogleDriveSourceConnection(latest, input.subjectId);
11299
- const updated = await updateConnection2(deps.db, {
11408
+ const latestMetadata = await requireGoogleDriveSourceConnection(deps, latest, input.subjectId);
11409
+ const updated = await transitionConnectionState(deps.db, {
11300
11410
  workspaceId: input.workspaceId,
11301
11411
  connectionId: latest.id,
11302
11412
  visibleToSubjectId: input.subjectId,
@@ -11407,11 +11517,60 @@ function requireGoogleDriveConnection(connection, subjectId) {
11407
11517
  }
11408
11518
  return parsed.data;
11409
11519
  }
11410
- function requireGoogleDriveSourceConnection(connection, subjectId) {
11520
+ function googleDriveLifecycle(state) {
11521
+ return GoogleDriveConnectionLifecycle.parse({
11522
+ state,
11523
+ recoverable: state !== "app_removed",
11524
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
11525
+ });
11526
+ }
11527
+ function effectiveGoogleDriveLifecycle(connection, metadata) {
11528
+ if (metadata.lifecycle) return metadata.lifecycle;
11529
+ if (connection.status === "revoked") return googleDriveLifecycle("disconnected");
11530
+ if (connection.status === "active") return googleDriveLifecycle("active");
11531
+ return googleDriveLifecycle("reconnect_required");
11532
+ }
11533
+ async function transitionGoogleDriveConnectionLifecycle(deps, connection, subjectId, lifecycle, status, lastError) {
11411
11534
  const metadata = requireGoogleDriveConnection(connection, subjectId);
11535
+ if (connection.status === status && metadata.lifecycle?.state === lifecycle.state && metadata.lifecycle.recoverable === lifecycle.recoverable) {
11536
+ return connection;
11537
+ }
11538
+ return await transitionConnectionState(deps.db, {
11539
+ workspaceId: connection.workspaceId,
11540
+ connectionId: connection.id,
11541
+ visibleToSubjectId: subjectId,
11542
+ expectedVersion: connection.version,
11543
+ status,
11544
+ metadata: GoogleDriveConnectionMetadata.parse({ ...metadata, lifecycle }),
11545
+ lastError,
11546
+ updatedBySubjectId: subjectId
11547
+ });
11548
+ }
11549
+ async function requireGoogleDriveSourceConnection(deps, connection, subjectId) {
11550
+ const metadata = requireGoogleDriveConnection(connection, subjectId);
11551
+ const lifecycle = effectiveGoogleDriveLifecycle(connection, metadata);
11552
+ if (connection.status === "revoked") {
11553
+ throw new HTTPException10(409, { message: "Google Drive is disconnected" });
11554
+ }
11555
+ if (lifecycle.state === "paused") {
11556
+ throw new HTTPException10(409, { message: "Google Drive is paused" });
11557
+ }
11558
+ if (connection.status !== "active" || lifecycle.state !== "active") {
11559
+ throw new HTTPException10(401, {
11560
+ message: lifecycle.state === "reconsent_required" ? "Google Drive needs permission re-consent" : lifecycle.state === "app_removed" ? "Google Drive app access is unavailable" : "Google Drive needs to be reconnected"
11561
+ });
11562
+ }
11412
11563
  if (!googleDriveScopesAllowCapability(connection.grantedScopes, "recursive_source_sync")) {
11564
+ await transitionGoogleDriveConnectionLifecycle(
11565
+ deps,
11566
+ connection,
11567
+ subjectId,
11568
+ googleDriveLifecycle("reconsent_required"),
11569
+ "needs_reauth",
11570
+ "google_drive_reconsent_required"
11571
+ );
11413
11572
  throw new HTTPException10(401, {
11414
- message: "Google Drive needs to be reconnected with selected-source read access"
11573
+ message: "Google Drive needs permission re-consent for selected-source read access"
11415
11574
  });
11416
11575
  }
11417
11576
  return metadata;
@@ -11522,8 +11681,93 @@ async function verifyGoogleDriveIdentity(accessToken, fetchImpl) {
11522
11681
  displayName: optionalString(user.displayName)
11523
11682
  };
11524
11683
  }
11684
+ function googleDriveRefreshFailureLifecycle(failure) {
11685
+ const code = failure.oauthErrorCode?.toLowerCase() ?? null;
11686
+ if (code === "invalid_client" || code === "unauthorized_client") {
11687
+ return {
11688
+ lifecycle: googleDriveLifecycle("app_removed"),
11689
+ status: "error",
11690
+ lastError: "google_drive_app_removed"
11691
+ };
11692
+ }
11693
+ if (code === "invalid_scope" || code === "insufficient_scope") {
11694
+ return {
11695
+ lifecycle: googleDriveLifecycle("reconsent_required"),
11696
+ status: "needs_reauth",
11697
+ lastError: "google_drive_reconsent_required"
11698
+ };
11699
+ }
11700
+ if (code === "invalid_grant") {
11701
+ return {
11702
+ lifecycle: googleDriveLifecycle("token_revoked"),
11703
+ status: "needs_reauth",
11704
+ lastError: "google_drive_token_revoked"
11705
+ };
11706
+ }
11707
+ return {
11708
+ lifecycle: googleDriveLifecycle("reconnect_required"),
11709
+ status: "needs_reauth",
11710
+ lastError: "google_drive_reconnect_required"
11711
+ };
11712
+ }
11713
+ async function transitionGoogleDrivePermanentRefreshFailure(deps, failure) {
11714
+ if (failure.providerDomain !== GOOGLE_DRIVE_PROVIDER_DOMAIN || !failure.subjectId) {
11715
+ return false;
11716
+ }
11717
+ const connection = await getConnectionMetadata2(
11718
+ deps.db,
11719
+ failure.workspaceId,
11720
+ failure.connectionId,
11721
+ failure.subjectId
11722
+ );
11723
+ if (!connection || connection.version !== failure.connectionVersion) {
11724
+ return true;
11725
+ }
11726
+ const transition = googleDriveRefreshFailureLifecycle(failure);
11727
+ await transitionGoogleDriveConnectionLifecycle(
11728
+ deps,
11729
+ connection,
11730
+ failure.subjectId,
11731
+ transition.lifecycle,
11732
+ transition.status,
11733
+ transition.lastError
11734
+ );
11735
+ return true;
11736
+ }
11737
+ async function transitionGoogleDriveProviderResponseFailure(deps, input) {
11738
+ const latest = await getConnectionMetadata2(
11739
+ deps.db,
11740
+ input.workspaceId,
11741
+ input.connectionId,
11742
+ input.subjectId
11743
+ );
11744
+ if (!latest || latest.version !== input.connectionVersion || latest.status !== "active") {
11745
+ return;
11746
+ }
11747
+ await transitionGoogleDriveConnectionLifecycle(
11748
+ deps,
11749
+ latest,
11750
+ input.subjectId,
11751
+ input.lifecycle,
11752
+ input.status,
11753
+ input.lastError
11754
+ );
11755
+ }
11525
11756
  async function googleDriveApiRequest(deps, input) {
11526
- const resolver = buildConnectionTokenResolver3(deps.db, deps.settings);
11757
+ const current = await getConnectionMetadata2(
11758
+ deps.db,
11759
+ input.workspaceId,
11760
+ input.connectionId,
11761
+ input.subjectId
11762
+ );
11763
+ if (!current) {
11764
+ throw new HTTPException10(404, { message: "Google Drive connection not found" });
11765
+ }
11766
+ await requireGoogleDriveSourceConnection(deps, current, input.subjectId);
11767
+ const resolver = buildConnectionTokenResolver3(deps.db, deps.settings, void 0, {
11768
+ ...deps.googleDriveFetch ? { refreshTransport: { fetchImpl: deps.googleDriveFetch } } : {},
11769
+ transitionPermanentRefreshFailure: async (failure) => await transitionGoogleDrivePermanentRefreshFailure(deps, failure)
11770
+ });
11527
11771
  const resolve = async (forceRefresh) => await resolver({
11528
11772
  workspaceId: input.workspaceId,
11529
11773
  subjectId: input.subjectId,
@@ -11542,6 +11786,10 @@ async function googleDriveApiRequest(deps, input) {
11542
11786
  if (credential.status !== "ok") {
11543
11787
  throw new HTTPException10(401, { message: "Google Drive needs to be reconnected" });
11544
11788
  }
11789
+ let providerConnectionVersion = credential.connectionVersion;
11790
+ if (providerConnectionVersion === void 0) {
11791
+ throw new Error("Google Drive credential resolver omitted the connection version");
11792
+ }
11545
11793
  const fetchImpl = deps.googleDriveFetch ?? fetch;
11546
11794
  let response = await providerFetch(fetchImpl, input.url, {
11547
11795
  headers: { ...credential.headers, accept: "application/json" }
@@ -11552,14 +11800,45 @@ async function googleDriveApiRequest(deps, input) {
11552
11800
  if (credential.status !== "ok") {
11553
11801
  throw new HTTPException10(401, { message: "Google Drive needs to be reconnected" });
11554
11802
  }
11803
+ providerConnectionVersion = credential.connectionVersion;
11804
+ if (providerConnectionVersion === void 0) {
11805
+ throw new Error("Google Drive credential resolver omitted the connection version");
11806
+ }
11555
11807
  response = await providerFetch(fetchImpl, input.url, {
11556
11808
  headers: { ...credential.headers, accept: "application/json" }
11557
11809
  });
11558
11810
  }
11559
11811
  if (!response.ok) {
11560
- await response.body?.cancel().catch(() => void 0);
11812
+ if (response.status === 401) {
11813
+ await response.body?.cancel().catch(() => void 0);
11814
+ await transitionGoogleDriveProviderResponseFailure(deps, {
11815
+ workspaceId: input.workspaceId,
11816
+ subjectId: input.subjectId,
11817
+ connectionId: input.connectionId,
11818
+ connectionVersion: providerConnectionVersion,
11819
+ lifecycle: googleDriveLifecycle("reconnect_required"),
11820
+ status: "needs_reauth",
11821
+ lastError: "google_drive_reconnect_required"
11822
+ });
11823
+ throw new HTTPException10(401, { message: "Google Drive needs to be reconnected" });
11824
+ }
11825
+ const providerErrorCode = response.status === 403 ? await readGoogleDriveProviderErrorCode(response) : null;
11826
+ if (response.status !== 403) {
11827
+ await response.body?.cancel().catch(() => void 0);
11828
+ }
11829
+ if (response.status === 403 && providerErrorCode && GOOGLE_DRIVE_RECONSENT_ERROR_CODES.has(providerErrorCode)) {
11830
+ await transitionGoogleDriveProviderResponseFailure(deps, {
11831
+ workspaceId: input.workspaceId,
11832
+ subjectId: input.subjectId,
11833
+ connectionId: input.connectionId,
11834
+ connectionVersion: providerConnectionVersion,
11835
+ lifecycle: googleDriveLifecycle("reconsent_required"),
11836
+ status: "needs_reauth",
11837
+ lastError: "google_drive_reconsent_required"
11838
+ });
11839
+ }
11561
11840
  throw new HTTPException10(response.status === 403 ? 403 : 502, {
11562
- message: response.status === 403 ? "Google Drive denied metadata access; reconnect and approve the requested scope" : "Google Drive metadata request failed"
11841
+ message: response.status === 403 ? "Google Drive denied metadata access; re-consent may be required" : "Google Drive metadata request failed"
11563
11842
  });
11564
11843
  }
11565
11844
  return await readResponseJsonBounded5(response, GOOGLE_RESPONSE_MAX_BYTES, input.label);
@@ -11575,6 +11854,24 @@ async function providerFetch(fetchImpl, url, init) {
11575
11854
  throw new HTTPException10(502, { message: "Google Drive is temporarily unavailable" });
11576
11855
  }
11577
11856
  }
11857
+ async function readGoogleDriveProviderErrorCode(response) {
11858
+ try {
11859
+ const payload = objectRecord(
11860
+ await readResponseJsonBounded5(
11861
+ response,
11862
+ GOOGLE_RESPONSE_MAX_BYTES,
11863
+ "Google Drive error response"
11864
+ )
11865
+ );
11866
+ const error = objectRecord(payload.error);
11867
+ const first = Array.isArray(error.errors) ? objectRecord(error.errors[0]) : {};
11868
+ const code = optionalString(first.reason) ?? optionalString(error.status);
11869
+ return code && /^[A-Za-z0-9_.-]{1,64}$/.test(code) ? code : null;
11870
+ } catch {
11871
+ await response.body?.cancel().catch(() => void 0);
11872
+ return null;
11873
+ }
11874
+ }
11578
11875
  function parseDriveItem(value) {
11579
11876
  const item = objectRecord(value);
11580
11877
  const id = optionalString(item.id);
@@ -11864,6 +12161,28 @@ function registerConnectionRoutes(app, deps) {
11864
12161
  });
11865
12162
  return c.redirect(result.redirectTo, 302);
11866
12163
  });
12164
+ app.patch(
12165
+ "/v1/workspaces/:workspaceId/connections/google-drive/:connectionId/lifecycle",
12166
+ async (c) => {
12167
+ assertIntegrationsEnabled();
12168
+ const workspaceId = c.req.param("workspaceId");
12169
+ const grant = await requireAccessGrant3(c, deps, workspaceId, "connections:write");
12170
+ const parsed = GoogleDriveLifecycleActionRequest.safeParse(await c.req.json());
12171
+ if (!parsed.success) {
12172
+ throw new HTTPException11(400, { message: "invalid Google Drive lifecycle request" });
12173
+ }
12174
+ return c.json(
12175
+ ConnectionResponse.parse({
12176
+ connection: await transitionGoogleDriveLifecycle(deps, {
12177
+ workspaceId,
12178
+ subjectId: grant.subjectId,
12179
+ connectionId: c.req.param("connectionId"),
12180
+ payload: parsed.data
12181
+ })
12182
+ })
12183
+ );
12184
+ }
12185
+ );
11867
12186
  app.get(
11868
12187
  "/v1/workspaces/:workspaceId/connections/google-drive/:connectionId/browse",
11869
12188
  async (c) => {
@@ -11980,7 +12299,22 @@ function registerConnectionRoutes(app, deps) {
11980
12299
  if (!existing) {
11981
12300
  throw new HTTPException11(404, { message: "connection not found" });
11982
12301
  }
11983
- const connection = isOpenGeniSlackBotConnection2(existing) ? await revokeConnectionWithSlackBotSuccessAudit(db, {
12302
+ const isGoogleDrive = existing.subjectId === grant.subjectId && existing.providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN2 && existing.kind === "oauth2" && GoogleDriveConnectionMetadata2.safeParse(existing.metadata).success;
12303
+ const googleDriveDisconnect = isGoogleDrive ? GoogleDriveDisconnectRequest.safeParse(await c.req.json().catch(() => null)) : null;
12304
+ if (googleDriveDisconnect && !googleDriveDisconnect.success) {
12305
+ throw new HTTPException11(400, {
12306
+ message: googleDriveDisconnect.error.issues[0]?.message ?? "invalid Google Drive disconnect request"
12307
+ });
12308
+ }
12309
+ if (existing.status === "revoked" && !isGoogleDrive) {
12310
+ return c.json(ConnectionResponse.parse({ connection: existing }));
12311
+ }
12312
+ const connection = isGoogleDrive ? await disconnectGoogleDrive(deps, {
12313
+ workspaceId,
12314
+ subjectId: grant.subjectId,
12315
+ connection: existing,
12316
+ payload: googleDriveDisconnect.data
12317
+ }) : isOpenGeniSlackBotConnection2(existing) ? await revokeConnectionWithSlackBotSuccessAudit(db, {
11984
12318
  accountId: grant.accountId,
11985
12319
  workspaceId,
11986
12320
  subjectId: grant.subjectId,
@@ -11989,7 +12323,7 @@ function registerConnectionRoutes(app, deps) {
11989
12323
  credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE2,
11990
12324
  credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL2,
11991
12325
  slackTeamId: openGeniSlackBotMetadata2(existing.metadata).slackTeamId
11992
- }) : await revokeConnection(db, workspaceId, connectionId, grant.subjectId);
12326
+ }) : await revokeConnection(db, workspaceId, connectionId, grant.subjectId, existing.version);
11993
12327
  if (!connection) {
11994
12328
  throw new HTTPException11(409, { message: "connection changed during disconnect; try again" });
11995
12329
  }
@@ -12326,6 +12660,7 @@ import {
12326
12660
  Document,
12327
12661
  DocumentBase,
12328
12662
  DocumentSearchRequest,
12663
+ DocumentSearchResponse as DocumentSearchResponse2,
12329
12664
  KnowledgeMemory,
12330
12665
  KnowledgeMemorySearchRequest,
12331
12666
  MoveDocumentRequest,
@@ -12354,18 +12689,19 @@ import {
12354
12689
  listDocuments,
12355
12690
  moveDocumentToBase,
12356
12691
  queueDocumentForReindex,
12357
- searchDocuments as searchDocuments2
12692
+ searchEffectiveDocuments as searchEffectiveDocuments2
12358
12693
  } from "@opengeni/documents";
12359
12694
  import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
12360
12695
  import { HTTPException as HTTPException13 } from "hono/http-exception";
12361
- import { requireAccessGrant as requireAccessGrant5 } from "@opengeni/core";
12696
+ import { requireAccessGrant as requireAccessGrant5, requireAccessGrantAuthorization } from "@opengeni/core";
12362
12697
  import { recordWorkspaceUsage as recordWorkspaceUsage3, requireLimit as requireLimit3 } from "@opengeni/core";
12363
12698
 
12364
12699
  // src/mcp/documents.ts
12700
+ import { DocumentSearchResponse } from "@opengeni/contracts";
12365
12701
  import {
12366
12702
  getDocumentChunk,
12367
12703
  listDocumentBases,
12368
- searchDocuments
12704
+ searchEffectiveDocuments
12369
12705
  } from "@opengeni/documents";
12370
12706
  import { createKnowledgeMemory, listKnowledgeMemories } from "@opengeni/db";
12371
12707
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -12397,14 +12733,14 @@ var SourceRefSchema = z2.object({
12397
12733
  title: z2.string().min(1).optional(),
12398
12734
  metadata: z2.record(z2.string(), z2.unknown()).optional()
12399
12735
  });
12400
- function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, options = {}) {
12736
+ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, options) {
12401
12737
  const server = new McpServer2({
12402
12738
  name: "opengeni-documents",
12403
12739
  version: "1.0.0"
12404
12740
  });
12405
12741
  const agentAccess = {
12406
12742
  agentOnly: true,
12407
- ...options.viewerSubjectId ? { viewerSubjectId: options.viewerSubjectId } : {}
12743
+ viewerSubjectId: options.initiatingSubjectId
12408
12744
  };
12409
12745
  server.registerTool(
12410
12746
  "list_document_bases",
@@ -12422,15 +12758,31 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
12422
12758
  description: "Search indexed documents with hybrid, vector, or keyword retrieval.",
12423
12759
  inputSchema: SearchInputSchema
12424
12760
  },
12425
- async (input) => searchContent(db, workspaceId, documentServices, input, agentAccess)
12761
+ async (input) => searchContent(
12762
+ db,
12763
+ accountId,
12764
+ workspaceId,
12765
+ documentServices,
12766
+ input,
12767
+ options.initiatingSubjectId,
12768
+ false
12769
+ )
12426
12770
  );
12427
12771
  server.registerTool(
12428
12772
  "knowledge_search",
12429
12773
  {
12430
- description: "Search company knowledge sources with optional base, source-kind, ACL, and retrieval-mode filters.",
12774
+ description: "Search the effective authorized organization, current-workspace, and immutable initiating-user personal document scope. Authorization is applied before ranking and every result retains source and authority provenance.",
12431
12775
  inputSchema: SearchInputSchema
12432
12776
  },
12433
- async (input) => searchContent(db, workspaceId, documentServices, input, agentAccess)
12777
+ async (input) => searchContent(
12778
+ db,
12779
+ accountId,
12780
+ workspaceId,
12781
+ documentServices,
12782
+ input,
12783
+ options.initiatingSubjectId,
12784
+ true
12785
+ )
12434
12786
  );
12435
12787
  server.registerTool(
12436
12788
  "fetch_document_chunk",
@@ -12441,7 +12793,7 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
12441
12793
  }
12442
12794
  },
12443
12795
  async ({ chunkId }) => {
12444
- const found = await getDocumentChunk(db, workspaceId, chunkId, agentAccess);
12796
+ const found = await getDocumentChunk(db, accountId, workspaceId, chunkId, agentAccess);
12445
12797
  return {
12446
12798
  content: [
12447
12799
  { type: "text", text: found ? JSON.stringify(found) : `chunk not found: ${chunkId}` }
@@ -12459,7 +12811,7 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
12459
12811
  }
12460
12812
  },
12461
12813
  async ({ chunkId }) => {
12462
- const found = await getDocumentChunk(db, workspaceId, chunkId, agentAccess);
12814
+ const found = await getDocumentChunk(db, accountId, workspaceId, chunkId, agentAccess);
12463
12815
  return {
12464
12816
  content: [
12465
12817
  { type: "text", text: found ? JSON.stringify(found) : `chunk not found: ${chunkId}` }
@@ -12536,27 +12888,28 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
12536
12888
  );
12537
12889
  return server;
12538
12890
  }
12539
- async function searchContent(db, workspaceId, documentServices, input, access) {
12891
+ async function searchContent(db, accountId, workspaceId, documentServices, input, initiatingSubjectId, wrapResponse) {
12892
+ const results = await searchEffectiveDocuments(
12893
+ db,
12894
+ {
12895
+ accountId,
12896
+ workspaceId,
12897
+ query: input.query,
12898
+ ...input.baseIds ? { baseIds: input.baseIds } : {},
12899
+ ...input.limit ? { limit: input.limit } : {},
12900
+ ...input.mode ? { mode: input.mode } : {},
12901
+ ...input.sourceKinds ? { sourceKinds: input.sourceKinds } : {},
12902
+ ...input.aclTags ? { aclTags: input.aclTags } : {},
12903
+ initiatingSubjectId,
12904
+ surface: "agent"
12905
+ },
12906
+ documentServices
12907
+ );
12540
12908
  return {
12541
12909
  content: [
12542
12910
  {
12543
12911
  type: "text",
12544
- text: JSON.stringify(
12545
- await searchDocuments(
12546
- db,
12547
- {
12548
- workspaceId,
12549
- query: input.query,
12550
- ...input.baseIds ? { baseIds: input.baseIds } : {},
12551
- ...input.limit ? { limit: input.limit } : {},
12552
- ...input.mode ? { mode: input.mode } : {},
12553
- ...input.sourceKinds ? { sourceKinds: input.sourceKinds } : {},
12554
- ...input.aclTags ? { aclTags: input.aclTags } : {},
12555
- access
12556
- },
12557
- documentServices
12558
- )
12559
- )
12912
+ text: JSON.stringify(wrapResponse ? DocumentSearchResponse.parse({ results }) : results)
12560
12913
  }
12561
12914
  ]
12562
12915
  };
@@ -13047,7 +13400,8 @@ function registerDocumentRoutes(app, deps) {
13047
13400
  });
13048
13401
  app.post("/v1/workspaces/:workspaceId/document-bases/:baseId/documents", async (c) => {
13049
13402
  const workspaceId = c.req.param("workspaceId");
13050
- const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:manage");
13403
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "documents:manage");
13404
+ const { grant } = access;
13051
13405
  if (!objectStorage) {
13052
13406
  throw new HTTPException13(503, { message: "object storage is not configured" });
13053
13407
  }
@@ -13058,6 +13412,10 @@ function registerDocumentRoutes(app, deps) {
13058
13412
  quantity: 0
13059
13413
  });
13060
13414
  const payload = AddDocumentRequest.parse(await c.req.json());
13415
+ const organizationAuthorityGranted = access.accountGrant?.permissions.includes("account:admin") === true;
13416
+ if (payload.authorityKind === "organization" && !organizationAuthorityGranted) {
13417
+ throw new HTTPException13(403, { message: "missing permission: account:admin" });
13418
+ }
13061
13419
  try {
13062
13420
  const document = await addDocumentToBase(db, {
13063
13421
  ...payload,
@@ -13065,13 +13423,18 @@ function registerDocumentRoutes(app, deps) {
13065
13423
  workspaceId,
13066
13424
  baseId: c.req.param("baseId"),
13067
13425
  createdBy: grant.subjectId,
13426
+ initiatingSubjectId: grant.subjectId,
13427
+ organizationAuthorityGranted,
13068
13428
  access: { viewerSubjectId: grant.subjectId }
13069
13429
  });
13070
13430
  const wasCreated = document.status === "queued" && document.chunkCount === 0 && document.error === null;
13071
13431
  const indexed = document.status === "ready" ? document : await documentIndexer.indexDocument({
13072
13432
  accountId: grant.accountId,
13073
13433
  workspaceId,
13074
- documentId: document.id
13434
+ documentId: document.id,
13435
+ authorityKind: document.authorityKind,
13436
+ authorityWorkspaceId: document.authorityWorkspaceId,
13437
+ authoritySubjectId: document.authoritySubjectId
13075
13438
  }) ?? document;
13076
13439
  if (indexed.status === "ready") {
13077
13440
  await recordWorkspaceUsage3(deps, {
@@ -13104,13 +13467,28 @@ function registerDocumentRoutes(app, deps) {
13104
13467
  "/v1/workspaces/:workspaceId/document-bases/:baseId/documents/:documentId",
13105
13468
  async (c) => {
13106
13469
  const workspaceId = c.req.param("workspaceId");
13107
- const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:manage");
13470
+ const authorization = await requireAccessGrantAuthorization(
13471
+ c,
13472
+ deps,
13473
+ workspaceId,
13474
+ "documents:manage"
13475
+ );
13476
+ const { grant } = authorization;
13477
+ const organizationAuthorityGranted = hasAccountAdminAuthority(authorization);
13108
13478
  try {
13479
+ const document = await getDocument(db, workspaceId, c.req.param("documentId"), {
13480
+ viewerSubjectId: grant.subjectId
13481
+ });
13482
+ if (!document || document.baseId !== c.req.param("baseId")) {
13483
+ throw new HTTPException13(404, { message: "document not found" });
13484
+ }
13485
+ requireOrganizationDocumentAuthority(document.authorityKind, organizationAuthorityGranted);
13109
13486
  await deleteDocumentFromBase(db, {
13110
13487
  accountId: grant.accountId,
13111
13488
  workspaceId,
13112
13489
  baseId: c.req.param("baseId"),
13113
13490
  documentId: c.req.param("documentId"),
13491
+ organizationAuthorityGranted,
13114
13492
  access: { viewerSubjectId: grant.subjectId }
13115
13493
  });
13116
13494
  return c.body(null, 204);
@@ -13126,7 +13504,14 @@ function registerDocumentRoutes(app, deps) {
13126
13504
  "/v1/workspaces/:workspaceId/document-bases/:baseId/documents/:documentId/reindex",
13127
13505
  async (c) => {
13128
13506
  const workspaceId = c.req.param("workspaceId");
13129
- const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:manage");
13507
+ const authorization = await requireAccessGrantAuthorization(
13508
+ c,
13509
+ deps,
13510
+ workspaceId,
13511
+ "documents:manage"
13512
+ );
13513
+ const { grant } = authorization;
13514
+ const organizationAuthorityGranted = hasAccountAdminAuthority(authorization);
13130
13515
  if (!objectStorage) {
13131
13516
  throw new HTTPException13(503, { message: "object storage is not configured" });
13132
13517
  }
@@ -13143,19 +13528,29 @@ function registerDocumentRoutes(app, deps) {
13143
13528
  if (!document) {
13144
13529
  throw new HTTPException13(404, { message: "document not found" });
13145
13530
  }
13531
+ requireOrganizationDocumentAuthority(document.authorityKind, organizationAuthorityGranted);
13146
13532
  if (document.status !== "failed") {
13147
13533
  throw new HTTPException13(422, { message: "only failed documents can be retried" });
13148
13534
  }
13149
13535
  if (document.baseId !== c.req.param("baseId")) {
13150
13536
  throw new HTTPException13(404, { message: "document not found" });
13151
13537
  }
13152
- const queued = await queueDocumentForReindex(db, workspaceId, document.id, {
13153
- viewerSubjectId: grant.subjectId
13154
- });
13538
+ const queued = await queueDocumentForReindex(
13539
+ db,
13540
+ workspaceId,
13541
+ document.id,
13542
+ {
13543
+ viewerSubjectId: grant.subjectId
13544
+ },
13545
+ organizationAuthorityGranted
13546
+ );
13155
13547
  const indexed = await documentIndexer.indexDocument({
13156
13548
  accountId: grant.accountId,
13157
13549
  workspaceId,
13158
- documentId: document.id
13550
+ documentId: document.id,
13551
+ authorityKind: document.authorityKind,
13552
+ authorityWorkspaceId: document.authorityWorkspaceId,
13553
+ authoritySubjectId: document.authoritySubjectId
13159
13554
  }) ?? queued;
13160
13555
  if (indexed.status === "ready") {
13161
13556
  await recordWorkspaceUsage3(deps, {
@@ -13187,47 +13582,56 @@ function registerDocumentRoutes(app, deps) {
13187
13582
  if (!base) {
13188
13583
  throw new HTTPException13(404, { message: "document base not found" });
13189
13584
  }
13190
- return c.json({
13191
- results: await searchDocuments2(
13192
- db,
13193
- {
13194
- workspaceId,
13195
- baseIds: [base.id],
13196
- query: payload.query,
13197
- limit: payload.limit,
13198
- mode: payload.mode,
13199
- sourceKinds: payload.sourceKinds,
13200
- aclTags: payload.aclTags,
13201
- access: { viewerSubjectId: grant.subjectId }
13202
- },
13203
- getDocumentServices()
13204
- )
13205
- });
13585
+ return c.json(
13586
+ DocumentSearchResponse2.parse({
13587
+ results: await searchEffectiveDocuments2(
13588
+ db,
13589
+ {
13590
+ accountId: grant.accountId,
13591
+ workspaceId,
13592
+ baseIds: [base.id],
13593
+ query: payload.query,
13594
+ limit: payload.limit,
13595
+ mode: payload.mode,
13596
+ sourceKinds: payload.sourceKinds,
13597
+ aclTags: payload.aclTags,
13598
+ initiatingSubjectId: grant.subjectId,
13599
+ surface: "human"
13600
+ },
13601
+ getDocumentServices()
13602
+ )
13603
+ })
13604
+ );
13206
13605
  });
13207
13606
  app.post("/v1/workspaces/:workspaceId/knowledge/search", async (c) => {
13208
13607
  const workspaceId = c.req.param("workspaceId");
13209
13608
  const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:search");
13210
13609
  const payload = await parseDocumentSearchRequest(c, "invalid knowledge search request");
13211
- return c.json({
13212
- results: await searchDocuments2(
13213
- db,
13214
- {
13215
- workspaceId,
13216
- query: payload.query,
13217
- baseIds: payload.baseIds,
13218
- limit: payload.limit,
13219
- mode: payload.mode,
13220
- sourceKinds: payload.sourceKinds,
13221
- aclTags: payload.aclTags,
13222
- access: { viewerSubjectId: grant.subjectId }
13223
- },
13224
- getDocumentServices()
13225
- )
13226
- });
13610
+ return c.json(
13611
+ DocumentSearchResponse2.parse({
13612
+ results: await searchEffectiveDocuments2(
13613
+ db,
13614
+ {
13615
+ accountId: grant.accountId,
13616
+ workspaceId,
13617
+ query: payload.query,
13618
+ baseIds: payload.baseIds,
13619
+ limit: payload.limit,
13620
+ mode: payload.mode,
13621
+ sourceKinds: payload.sourceKinds,
13622
+ aclTags: payload.aclTags,
13623
+ initiatingSubjectId: grant.subjectId,
13624
+ surface: "human"
13625
+ },
13626
+ getDocumentServices()
13627
+ )
13628
+ })
13629
+ );
13227
13630
  });
13228
13631
  app.post("/v1/workspaces/:workspaceId/knowledge/drops", async (c) => {
13229
13632
  const workspaceId = c.req.param("workspaceId");
13230
- const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:manage");
13633
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "documents:manage");
13634
+ const { grant } = access;
13231
13635
  if (!objectStorage) {
13232
13636
  throw new HTTPException13(503, { message: "object storage is not configured" });
13233
13637
  }
@@ -13238,6 +13642,10 @@ function registerDocumentRoutes(app, deps) {
13238
13642
  quantity: 0
13239
13643
  });
13240
13644
  const payload = CreateKnowledgeDropRequest.parse(await c.req.json());
13645
+ const organizationAuthorityGranted = access.accountGrant?.permissions.includes("account:admin") === true;
13646
+ if (payload.authorityKind === "organization" && !organizationAuthorityGranted) {
13647
+ throw new HTTPException13(403, { message: "missing permission: account:admin" });
13648
+ }
13241
13649
  try {
13242
13650
  let fileId;
13243
13651
  if (payload.text !== void 0) {
@@ -13298,12 +13706,15 @@ function registerDocumentRoutes(app, deps) {
13298
13706
  const document = await addDocumentToBase(db, {
13299
13707
  fileId,
13300
13708
  ...payload.title ? { title: payload.title } : {},
13709
+ ...payload.authorityKind ? { authorityKind: payload.authorityKind } : {},
13301
13710
  ...payload.visibility ? { visibility: payload.visibility } : {},
13302
13711
  ...payload.agentAccess !== void 0 ? { agentAccess: payload.agentAccess } : {},
13303
13712
  accountId: grant.accountId,
13304
13713
  workspaceId,
13305
13714
  baseId: defaultBase.id,
13306
13715
  createdBy: grant.subjectId,
13716
+ initiatingSubjectId: grant.subjectId,
13717
+ organizationAuthorityGranted,
13307
13718
  curationStatus: "pending",
13308
13719
  access: { viewerSubjectId: grant.subjectId }
13309
13720
  });
@@ -13311,7 +13722,10 @@ function registerDocumentRoutes(app, deps) {
13311
13722
  const indexed = document.status === "ready" ? document : await documentIndexer.indexDocument({
13312
13723
  accountId: grant.accountId,
13313
13724
  workspaceId,
13314
- documentId: document.id
13725
+ documentId: document.id,
13726
+ authorityKind: document.authorityKind,
13727
+ authorityWorkspaceId: document.authorityWorkspaceId,
13728
+ authoritySubjectId: document.authoritySubjectId
13315
13729
  }) ?? document;
13316
13730
  if (indexed.status === "ready") {
13317
13731
  await recordWorkspaceUsage3(deps, {
@@ -13336,7 +13750,14 @@ function registerDocumentRoutes(app, deps) {
13336
13750
  });
13337
13751
  app.post("/v1/workspaces/:workspaceId/documents/:documentId/move", async (c) => {
13338
13752
  const workspaceId = c.req.param("workspaceId");
13339
- const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:manage");
13753
+ const authorization = await requireAccessGrantAuthorization(
13754
+ c,
13755
+ deps,
13756
+ workspaceId,
13757
+ "documents:manage"
13758
+ );
13759
+ const { grant } = authorization;
13760
+ const organizationAuthorityGranted = hasAccountAdminAuthority(authorization);
13340
13761
  const payload = MoveDocumentRequest.parse(await c.req.json().catch(() => ({})));
13341
13762
  try {
13342
13763
  const document = await getDocument(db, workspaceId, c.req.param("documentId"), {
@@ -13345,6 +13766,7 @@ function registerDocumentRoutes(app, deps) {
13345
13766
  if (!document) {
13346
13767
  throw new HTTPException13(404, { message: "document not found" });
13347
13768
  }
13769
+ requireOrganizationDocumentAuthority(document.authorityKind, organizationAuthorityGranted);
13348
13770
  return c.json(
13349
13771
  Document.parse(
13350
13772
  await moveDocumentToBase(db, {
@@ -13352,6 +13774,7 @@ function registerDocumentRoutes(app, deps) {
13352
13774
  workspaceId,
13353
13775
  documentId: document.id,
13354
13776
  targetBaseId: payload.targetBaseId ?? null,
13777
+ organizationAuthorityGranted,
13355
13778
  access: { viewerSubjectId: grant.subjectId }
13356
13779
  })
13357
13780
  )
@@ -13488,7 +13911,7 @@ function registerDocumentRoutes(app, deps) {
13488
13911
  grant.accountId,
13489
13912
  workspaceId,
13490
13913
  getDocumentServices(),
13491
- { createdBySessionId: sessionId, viewerSubjectId: grant.subjectId }
13914
+ { createdBySessionId: sessionId, initiatingSubjectId: grant.subjectId }
13492
13915
  );
13493
13916
  await server.connect(transport);
13494
13917
  return await transport.handleRequest(c.req.raw);
@@ -13507,6 +13930,9 @@ function dropFilename(preferred) {
13507
13930
  }
13508
13931
  function documentHttpException(error) {
13509
13932
  const message = error instanceof Error ? error.message : String(error);
13933
+ if (message.includes("organization document") && message.includes("exact account authority")) {
13934
+ return new HTTPException13(403, { message: "missing permission: account:admin" });
13935
+ }
13510
13936
  if (message.includes("not found")) {
13511
13937
  return new HTTPException13(404, { message });
13512
13938
  }
@@ -13524,6 +13950,14 @@ function documentHttpException(error) {
13524
13950
  }
13525
13951
  return new HTTPException13(500, { message });
13526
13952
  }
13953
+ function hasAccountAdminAuthority(authorization) {
13954
+ return authorization.accountGrant?.permissions.includes("account:admin") === true;
13955
+ }
13956
+ function requireOrganizationDocumentAuthority(authorityKind, organizationAuthorityGranted) {
13957
+ if (authorityKind === "organization" && !organizationAuthorityGranted) {
13958
+ throw new HTTPException13(403, { message: "missing permission: account:admin" });
13959
+ }
13960
+ }
13527
13961
 
13528
13962
  // src/routes/enrollments.ts
13529
13963
  import {
@@ -16679,6 +17113,10 @@ The initial conversation items are authoritative context from the current sessio
16679
17113
 
16680
17114
  Live context wrapped in <session_user_message> is an authoritative user message already routed to the current session. A status of queued_for_execution means it is waiting behind existing work; accepted_for_execution means it is next with no existing work ahead; accepted_for_steering means it was given priority as a change of direction, while any prior work may still be yielding. Incorporate it immediately as conversation context, but never delegate it again or treat the wrapper metadata as user-authored text.
16681
17115
 
17116
+ Live context wrapped in <session_human_input_request> means current work is paused for the user's answer. Preserve the exact question meaning and options. Ask one question at a time when useful. The user may answer in the visible form or answer conversationally. If the user answers conversationally, create exactly one delegation containing the relevant question and the user's answer so the session agent can continue with complete context. If the user changes direction instead, delegate the new direction normally. Do not claim work resumed until session context confirms it.
17117
+
17118
+ Live context wrapped in <session_human_input_response> is the authoritative outcome of that pending question. An answered or skipped response came through the structured session UI and is already routed; incorporate it, never delegate it again, and acknowledge briefly only if useful. An expired or cancelled response means the question is no longer active.
17119
+
16682
17120
  Live session updates may describe work that started before this realtime conversation, work sent directly by the user, or work delegated during an earlier realtime connection. Treat those updates as part of this same session even when they have no current delegation identity.
16683
17121
 
16684
17122
  ## Backend use
@@ -21426,9 +21864,11 @@ function requireAccountPermission(context, accountId, permission) {
21426
21864
  }
21427
21865
 
21428
21866
  // src/routes/workspace-instruction-policies.ts
21867
+ import { randomUUID } from "crypto";
21429
21868
  import {
21430
21869
  ActivateWorkspaceInstructionPolicyRequest,
21431
21870
  CreateWorkspaceInstructionPolicyDraftRequest,
21871
+ CreateWorkspaceInstructionPolicyOnboardingProposalRequest,
21432
21872
  ImportLegacyWorkspaceInstructionPolicyDraftRequest,
21433
21873
  RollbackWorkspaceInstructionPolicyRequest,
21434
21874
  WorkspaceInstructionPolicyActivationResponse,
@@ -21437,21 +21877,35 @@ import {
21437
21877
  WorkspaceInstructionPolicyDiffResponse,
21438
21878
  WorkspaceInstructionPolicyListQuery,
21439
21879
  WorkspaceInstructionPolicyListResponse,
21440
- WorkspaceInstructionPolicyRevision
21880
+ WorkspaceInstructionPolicyOnboardingProposal,
21881
+ WorkspaceInstructionPolicyOnboardingProposalConflictResponse,
21882
+ WorkspaceInstructionPolicyOnboardingProposalContentErrorResponse,
21883
+ WorkspaceInstructionPolicyOnboardingProposalListQuery,
21884
+ WorkspaceInstructionPolicyOnboardingProposalListResponse,
21885
+ WorkspaceInstructionPolicyOnboardingProposalStaleResponse,
21886
+ WorkspaceInstructionPolicyOperationReuseResponse,
21887
+ WorkspaceInstructionPolicyRevision,
21888
+ WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS
21441
21889
  } from "@opengeni/contracts";
21442
21890
  import { requireAccessGrant as requireAccessGrant17 } from "@opengeni/core";
21443
21891
  import {
21444
21892
  activateWorkspaceInstructionPolicyRevision,
21445
21893
  createWorkspaceInstructionPolicyDraft,
21894
+ createWorkspaceInstructionPolicyOnboardingProposal,
21446
21895
  diffWorkspaceInstructionPolicyRevisions,
21447
21896
  getWorkspaceInstructionPolicyRevision,
21448
21897
  importLegacyWorkspaceInstructionPolicyDraft,
21449
21898
  listWorkspaceInstructionPolicyRevisions,
21899
+ listWorkspaceInstructionPolicyOnboardingProposals,
21450
21900
  rollbackWorkspaceInstructionPolicyRevision,
21451
21901
  WorkspaceInstructionPolicyConflictError,
21452
21902
  WorkspaceInstructionPolicyInvalidOperationError,
21453
21903
  WorkspaceInstructionPolicyLegacyUnavailableError,
21454
- WorkspaceInstructionPolicyNotFoundError
21904
+ WorkspaceInstructionPolicyNotFoundError,
21905
+ WorkspaceInstructionPolicyOnboardingProposalConflictError,
21906
+ WorkspaceInstructionPolicyOnboardingProposalContentError,
21907
+ WorkspaceInstructionPolicyOnboardingProposalStaleError,
21908
+ WorkspaceInstructionPolicyOperationReuseError
21455
21909
  } from "@opengeni/db";
21456
21910
  import { HTTPException as HTTPException27 } from "hono/http-exception";
21457
21911
  import { z as z7 } from "zod";
@@ -21474,6 +21928,46 @@ function policyErrorResponse(context, error) {
21474
21928
  409
21475
21929
  );
21476
21930
  }
21931
+ if (error instanceof WorkspaceInstructionPolicyOperationReuseError) {
21932
+ return context.json(
21933
+ WorkspaceInstructionPolicyOperationReuseResponse.parse({
21934
+ code: error.code,
21935
+ message: error.message
21936
+ }),
21937
+ 409
21938
+ );
21939
+ }
21940
+ if (error instanceof WorkspaceInstructionPolicyOnboardingProposalContentError) {
21941
+ return context.json(
21942
+ WorkspaceInstructionPolicyOnboardingProposalContentErrorResponse.parse({
21943
+ code: error.code,
21944
+ message: error.message,
21945
+ maxChars: WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS
21946
+ }),
21947
+ 422
21948
+ );
21949
+ }
21950
+ if (error instanceof WorkspaceInstructionPolicyOnboardingProposalStaleError) {
21951
+ return context.json(
21952
+ WorkspaceInstructionPolicyOnboardingProposalStaleResponse.parse({
21953
+ code: error.code,
21954
+ message: error.message,
21955
+ currentHead: error.currentHead
21956
+ }),
21957
+ 409
21958
+ );
21959
+ }
21960
+ if (error instanceof WorkspaceInstructionPolicyOnboardingProposalConflictError) {
21961
+ return context.json(
21962
+ WorkspaceInstructionPolicyOnboardingProposalConflictResponse.parse({
21963
+ code: error.code,
21964
+ message: error.message,
21965
+ existingProposalId: error.existingProposalId,
21966
+ existingDraftRevisionId: error.existingDraftRevisionId
21967
+ }),
21968
+ 409
21969
+ );
21970
+ }
21477
21971
  if (error instanceof WorkspaceInstructionPolicyNotFoundError) {
21478
21972
  return context.json(
21479
21973
  { code: "WORKSPACE_INSTRUCTION_POLICY_NOT_FOUND", message: error.message },
@@ -21536,6 +22030,7 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21536
22030
  return context.json(
21537
22031
  WorkspaceInstructionPolicyRevision.parse(
21538
22032
  await createWorkspaceInstructionPolicyDraft(deps.db, {
22033
+ operationId: request.operationId ?? randomUUID(),
21539
22034
  accountId: grant.accountId,
21540
22035
  workspaceId,
21541
22036
  createdBySubjectId: grant.subjectId,
@@ -21563,6 +22058,7 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21563
22058
  return context.json(
21564
22059
  WorkspaceInstructionPolicyRevision.parse(
21565
22060
  await importLegacyWorkspaceInstructionPolicyDraft(deps.db, {
22061
+ operationId: request.operationId ?? randomUUID(),
21566
22062
  accountId: grant.accountId,
21567
22063
  workspaceId,
21568
22064
  createdBySubjectId: grant.subjectId,
@@ -21575,6 +22071,56 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21575
22071
  return policyErrorResponse(context, error);
21576
22072
  }
21577
22073
  });
22074
+ app.get(`${base}/onboarding-proposals`, async (context) => {
22075
+ const workspaceId = context.req.param("workspaceId");
22076
+ await requireAccessGrant17(context, deps, workspaceId, "workspace:read");
22077
+ const parsed = WorkspaceInstructionPolicyOnboardingProposalListQuery.safeParse({
22078
+ limit: context.req.query("limit")
22079
+ });
22080
+ if (!parsed.success) {
22081
+ throw new HTTPException27(422, {
22082
+ message: "Invalid workspace instruction-policy onboarding-proposal query"
22083
+ });
22084
+ }
22085
+ return context.json(
22086
+ WorkspaceInstructionPolicyOnboardingProposalListResponse.parse(
22087
+ await listWorkspaceInstructionPolicyOnboardingProposals(deps.db, workspaceId, parsed.data)
22088
+ )
22089
+ );
22090
+ });
22091
+ app.post(`${base}/onboarding-proposals`, async (context) => {
22092
+ const workspaceId = context.req.param("workspaceId");
22093
+ const grant = await requireAccessGrant17(context, deps, workspaceId, "workspace:admin");
22094
+ assertBoundedActor(grant.subjectId);
22095
+ const request = await parseBody(
22096
+ context,
22097
+ CreateWorkspaceInstructionPolicyOnboardingProposalRequest
22098
+ );
22099
+ try {
22100
+ return context.json(
22101
+ WorkspaceInstructionPolicyOnboardingProposal.parse(
22102
+ await createWorkspaceInstructionPolicyOnboardingProposal(deps.db, {
22103
+ operationId: request.operationId ?? randomUUID(),
22104
+ accountId: grant.accountId,
22105
+ workspaceId,
22106
+ createdBySubjectId: grant.subjectId,
22107
+ kind: request.kind,
22108
+ scope: request.scope,
22109
+ roleKey: request.roleKey,
22110
+ content: request.content,
22111
+ sourceId: request.sourceId,
22112
+ sourceVersion: request.sourceVersion,
22113
+ confidenceBps: request.confidenceBps,
22114
+ expectedCurrentRevisionId: request.expectedCurrentRevisionId,
22115
+ expectedActivationVersion: request.expectedActivationVersion
22116
+ })
22117
+ ),
22118
+ 201
22119
+ );
22120
+ } catch (error) {
22121
+ return policyErrorResponse(context, error);
22122
+ }
22123
+ });
21578
22124
  app.get(`${base}/diff`, async (context) => {
21579
22125
  const workspaceId = context.req.param("workspaceId");
21580
22126
  await requireAccessGrant17(context, deps, workspaceId, "workspace:read");
@@ -21604,10 +22150,12 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21604
22150
  return context.json(
21605
22151
  WorkspaceInstructionPolicyActivationResponse.parse(
21606
22152
  await rollbackWorkspaceInstructionPolicyRevision(deps.db, {
22153
+ operationId: request.operationId ?? randomUUID(),
21607
22154
  accountId: grant.accountId,
21608
22155
  workspaceId,
21609
22156
  targetRevisionId: request.targetRevisionId,
21610
22157
  expectedCurrentRevisionId: request.expectedCurrentRevisionId,
22158
+ ...request.expectedActivationVersion === void 0 ? {} : { expectedActivationVersion: request.expectedActivationVersion },
21611
22159
  actorSubjectId: grant.subjectId,
21612
22160
  reason: request.reason
21613
22161
  })
@@ -21641,10 +22189,12 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21641
22189
  return context.json(
21642
22190
  WorkspaceInstructionPolicyActivationResponse.parse(
21643
22191
  await activateWorkspaceInstructionPolicyRevision(deps.db, {
22192
+ operationId: request.operationId ?? randomUUID(),
21644
22193
  accountId: grant.accountId,
21645
22194
  workspaceId,
21646
22195
  revisionId,
21647
22196
  expectedCurrentRevisionId: request.expectedCurrentRevisionId,
22197
+ ...request.expectedActivationVersion === void 0 ? {} : { expectedActivationVersion: request.expectedActivationVersion },
21648
22198
  actorSubjectId: grant.subjectId,
21649
22199
  reason: request.reason
21650
22200
  })
@@ -21661,11 +22211,14 @@ import {
21661
22211
  WORKSPACE_STATE_MAX_BASES as WORKSPACE_STATE_MAX_BASES2,
21662
22212
  WORKSPACE_STATE_MAX_TOPICS as WORKSPACE_STATE_MAX_TOPICS2,
21663
22213
  WORKSPACE_STATE_TOPIC_MAX_CHARS as WORKSPACE_STATE_TOPIC_MAX_CHARS2,
22214
+ WorkspaceStateQuery,
21664
22215
  WorkspaceStateResponse as WorkspaceStateResponse2
21665
22216
  } from "@opengeni/contracts";
21666
22217
  import { hasPermission as hasPermission12, requireAccessGrant as requireAccessGrant18 } from "@opengeni/core";
21667
22218
  import {
21668
22219
  getWorkspace as getWorkspace2,
22220
+ getCurrentPreferenceRegistryGovernanceMetadata,
22221
+ getWorkspaceStateAcceptedAttemptGovernance,
21669
22222
  listWorkspaceStateMemoryRecords,
21670
22223
  listWorkspaceInstructionPolicyRevisions as listWorkspaceInstructionPolicyRevisions2
21671
22224
  } from "@opengeni/db";
@@ -21673,6 +22226,7 @@ import { getDocumentInventory } from "@opengeni/documents";
21673
22226
  import { HTTPException as HTTPException28 } from "hono/http-exception";
21674
22227
 
21675
22228
  // src/workspace-state-projection.ts
22229
+ import { createHash as createHash8 } from "crypto";
21676
22230
  import {
21677
22231
  KnowledgeMemoryKind,
21678
22232
  KnowledgeMemoryStatus,
@@ -21685,6 +22239,140 @@ import {
21685
22239
  WORKSPACE_STATE_TOPIC_MAX_CHARS,
21686
22240
  WorkspaceStateResponse
21687
22241
  } from "@opengeni/contracts";
22242
+ function hashIdentities(values) {
22243
+ return createHash8("sha256").update(values.join("\n"), "utf8").digest("hex");
22244
+ }
22245
+ function policyTargetKey(value) {
22246
+ return `${value.kind}:${value.scope}:${value.roleKey ?? ""}`;
22247
+ }
22248
+ function policyTargetKeysForRole(policyRole) {
22249
+ const keys = /* @__PURE__ */ new Set(["charter:global:", "policy:global:"]);
22250
+ if (policyRole !== null) keys.add(`policy:role:${policyRole}`);
22251
+ return keys;
22252
+ }
22253
+ function policyIdentity(value) {
22254
+ return `${policyTargetKey(value)}:${value.revisionId}:${value.contentHash}:${value.activationVersion}`;
22255
+ }
22256
+ function preferenceIdentity(value) {
22257
+ return `${value.scope}:${value.id}:${value.revisionId}:${value.contentHash}:${value.activeVersion}`;
22258
+ }
22259
+ function classifyIdentityDrift(snapshotIdentities, currentIdentities, snapshotKeys, currentKeys) {
22260
+ if (snapshotIdentities.join("\n") === currentIdentities.join("\n")) return "identical";
22261
+ return snapshotKeys.join("\n") === currentKeys.join("\n") ? "superseded" : "changed";
22262
+ }
22263
+ function overallDriftStatus(policy, preferences) {
22264
+ for (const status of ["unavailable", "truncated", "missing", "changed", "superseded"]) {
22265
+ if (policy === status || preferences === status) return status;
22266
+ }
22267
+ return "identical";
22268
+ }
22269
+ function attemptGovernanceProjection(input) {
22270
+ const governance = input.attemptGovernance ?? null;
22271
+ if (governance === null) return { status: "not_requested" };
22272
+ if (governance.status === "unavailable") {
22273
+ return {
22274
+ status: "unavailable",
22275
+ reason: "attempt_not_found_or_not_authorized",
22276
+ driftStatus: "unavailable"
22277
+ };
22278
+ }
22279
+ const policySnapshot = governance.policySnapshot;
22280
+ let policyStatus = "missing";
22281
+ let policySnapshotHash = null;
22282
+ let policyCurrentHash = null;
22283
+ let policySnapshotTargetCount = 0;
22284
+ let policyCurrentTargetCount = 0;
22285
+ if (policySnapshot) {
22286
+ const snapshotEntries = [...policySnapshot.entries].sort(
22287
+ (left, right) => policyTargetKey(left).localeCompare(policyTargetKey(right))
22288
+ );
22289
+ const snapshotKeys = snapshotEntries.map(policyTargetKey);
22290
+ const relevantTargetKeys = policyTargetKeysForRole(policySnapshot.policyRole);
22291
+ const currentEntries = input.policies.activeHeads.filter((head) => relevantTargetKeys.has(policyTargetKey(head))).sort((left, right) => policyTargetKey(left).localeCompare(policyTargetKey(right)));
22292
+ const snapshotIdentities = snapshotEntries.map(policyIdentity);
22293
+ const currentIdentities = currentEntries.map(policyIdentity);
22294
+ const currentKeys = currentEntries.map(policyTargetKey);
22295
+ policyStatus = classifyIdentityDrift(
22296
+ snapshotIdentities,
22297
+ currentIdentities,
22298
+ snapshotKeys,
22299
+ currentKeys
22300
+ );
22301
+ policySnapshotHash = hashIdentities(snapshotIdentities);
22302
+ policyCurrentHash = hashIdentities(currentIdentities);
22303
+ policySnapshotTargetCount = snapshotEntries.length;
22304
+ policyCurrentTargetCount = currentEntries.length;
22305
+ }
22306
+ const preferenceSnapshot = governance.preferenceSnapshot;
22307
+ const currentPreferences = [...governance.currentPreferences.descriptors].sort(
22308
+ (left, right) => preferenceIdentity(left).localeCompare(preferenceIdentity(right))
22309
+ );
22310
+ let preferenceStatus = "missing";
22311
+ let preferenceSnapshotHash = null;
22312
+ const currentPreferenceIdentities = currentPreferences.map(preferenceIdentity);
22313
+ const currentPreferenceHash = hashIdentities(currentPreferenceIdentities);
22314
+ let snapshotPreferenceCount = 0;
22315
+ let snapshotPreferenceTruncated = false;
22316
+ if (preferenceSnapshot) {
22317
+ const snapshotPreferences = [...preferenceSnapshot.descriptors].sort(
22318
+ (left, right) => preferenceIdentity(left).localeCompare(preferenceIdentity(right))
22319
+ );
22320
+ const snapshotPreferenceIdentities = snapshotPreferences.map(preferenceIdentity);
22321
+ const snapshotPreferenceKeys = snapshotPreferences.map((descriptor) => descriptor.id).sort();
22322
+ const currentPreferenceKeys = currentPreferences.map((descriptor) => descriptor.id).sort();
22323
+ preferenceSnapshotHash = hashIdentities(snapshotPreferenceIdentities);
22324
+ snapshotPreferenceCount = snapshotPreferences.length;
22325
+ snapshotPreferenceTruncated = preferenceSnapshot.truncated;
22326
+ preferenceStatus = preferenceSnapshot.truncated || governance.currentPreferences.truncated ? "truncated" : classifyIdentityDrift(
22327
+ snapshotPreferenceIdentities,
22328
+ currentPreferenceIdentities,
22329
+ snapshotPreferenceKeys,
22330
+ currentPreferenceKeys
22331
+ );
22332
+ }
22333
+ return {
22334
+ status: "available",
22335
+ attemptId: governance.attemptId,
22336
+ executionGeneration: governance.executionGeneration,
22337
+ acceptedAt: governance.acceptedAt,
22338
+ policySnapshot: policySnapshot ? {
22339
+ status: "available",
22340
+ id: policySnapshot.id,
22341
+ createdAt: policySnapshot.createdAt,
22342
+ entryHash: policySnapshot.entryHash,
22343
+ policyRole: policySnapshot.policyRole,
22344
+ roleSource: policySnapshot.roleSource,
22345
+ entries: policySnapshot.entries
22346
+ } : { status: "missing" },
22347
+ preferenceSnapshot: preferenceSnapshot ? {
22348
+ status: "available",
22349
+ id: preferenceSnapshot.id,
22350
+ createdAt: preferenceSnapshot.createdAt,
22351
+ descriptorHash: preferenceSnapshot.descriptorHash,
22352
+ descriptorCount: preferenceSnapshot.descriptors.length,
22353
+ truncated: preferenceSnapshot.truncated
22354
+ } : { status: "missing" },
22355
+ drift: {
22356
+ overall: overallDriftStatus(policyStatus, preferenceStatus),
22357
+ policy: {
22358
+ status: policyStatus,
22359
+ snapshotHash: policySnapshotHash,
22360
+ currentHash: policyCurrentHash,
22361
+ snapshotTargetCount: policySnapshotTargetCount,
22362
+ currentTargetCount: policyCurrentTargetCount
22363
+ },
22364
+ preferences: {
22365
+ status: preferenceStatus,
22366
+ snapshotHash: preferenceSnapshotHash,
22367
+ currentHash: currentPreferenceHash,
22368
+ snapshotDescriptorCount: snapshotPreferenceCount,
22369
+ currentDescriptorCount: currentPreferences.length,
22370
+ snapshotTruncated: snapshotPreferenceTruncated,
22371
+ currentTruncated: governance.currentPreferences.truncated
22372
+ }
22373
+ }
22374
+ };
22375
+ }
21688
22376
  function emptyMemoryStatusCounts() {
21689
22377
  return Object.fromEntries(
21690
22378
  KnowledgeMemoryStatus.options.map((status) => [status, 0])
@@ -21856,10 +22544,7 @@ function projectWorkspaceState(input) {
21856
22544
  generatedAt: input.generatedAt,
21857
22545
  truth: {
21858
22546
  current: { source: "read_time_projection", capturedAt: input.generatedAt },
21859
- policySnapshot: {
21860
- status: "not_captured",
21861
- reason: "workspace_instruction_policy_snapshot_not_implemented"
21862
- }
22547
+ attemptGovernance: attemptGovernanceProjection(input)
21863
22548
  },
21864
22549
  policy: policyProjection(input),
21865
22550
  knowledge: input.knowledge ? availableKnowledgeProjection(input.knowledge) : {
@@ -21874,10 +22559,11 @@ function projectWorkspaceState(input) {
21874
22559
  function registerWorkspaceStateRoutes(app, deps) {
21875
22560
  app.get("/v1/workspaces/:workspaceId/workspace-state", async (context) => {
21876
22561
  const workspaceId = context.req.param("workspaceId");
22562
+ const query = WorkspaceStateQuery.parse(context.req.query());
21877
22563
  const grant = await requireAccessGrant18(context, deps, workspaceId, "workspace:read");
21878
22564
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
21879
22565
  const canInspectKnowledge = hasPermission12(grant.permissions, "documents:search");
21880
- const [workspace, policies, knowledge] = await Promise.all([
22566
+ const [workspace, policies, knowledge, attemptGovernance] = await Promise.all([
21881
22567
  getWorkspace2(deps.db, workspaceId),
21882
22568
  listWorkspaceInstructionPolicyRevisions2(deps.db, workspaceId, { limit: 1 }),
21883
22569
  canInspectKnowledge ? (async () => {
@@ -21891,7 +22577,43 @@ function registerWorkspaceStateRoutes(app, deps) {
21891
22577
  listWorkspaceStateMemoryRecords(deps.db, workspaceId)
21892
22578
  ]);
21893
22579
  return { documents, memories };
21894
- })() : Promise.resolve(null)
22580
+ })() : Promise.resolve(null),
22581
+ query.attemptId ? getWorkspaceStateAcceptedAttemptGovernance(deps.db, {
22582
+ accountId: grant.accountId,
22583
+ workspaceId,
22584
+ subjectId: grant.subjectId,
22585
+ attemptId: query.attemptId
22586
+ }).then(async (snapshot) => {
22587
+ if (!snapshot) return { status: "unavailable" };
22588
+ const currentPreferences = await getCurrentPreferenceRegistryGovernanceMetadata(
22589
+ deps.db,
22590
+ {
22591
+ workspaceId,
22592
+ subjectId: grant.subjectId
22593
+ }
22594
+ );
22595
+ return {
22596
+ status: "available",
22597
+ attemptId: snapshot.attemptId,
22598
+ executionGeneration: snapshot.executionGeneration,
22599
+ acceptedAt: snapshot.acceptedAt,
22600
+ policySnapshot: snapshot.policySnapshot,
22601
+ preferenceSnapshot: snapshot.preferenceSnapshot ? {
22602
+ id: snapshot.preferenceSnapshot.id,
22603
+ descriptorHash: snapshot.preferenceSnapshot.descriptorHash,
22604
+ descriptors: snapshot.preferenceSnapshot.descriptors.map((descriptor) => ({
22605
+ id: descriptor.id,
22606
+ revisionId: descriptor.revisionId,
22607
+ contentHash: descriptor.contentHash,
22608
+ activeVersion: descriptor.activeVersion,
22609
+ scope: descriptor.scope
22610
+ })),
22611
+ truncated: snapshot.preferenceSnapshot.truncated,
22612
+ createdAt: snapshot.preferenceSnapshot.createdAt
22613
+ } : null,
22614
+ currentPreferences
22615
+ };
22616
+ }) : Promise.resolve(null)
21895
22617
  ]);
21896
22618
  if (!workspace) {
21897
22619
  throw new HTTPException28(404, { message: "workspace not found" });
@@ -21904,7 +22626,8 @@ function registerWorkspaceStateRoutes(app, deps) {
21904
22626
  generatedAt,
21905
22627
  workspaceAgentInstructions: workspace.agentInstructions,
21906
22628
  policies,
21907
- knowledge
22629
+ knowledge,
22630
+ attemptGovernance
21908
22631
  })
21909
22632
  )
21910
22633
  );
@@ -21912,7 +22635,7 @@ function registerWorkspaceStateRoutes(app, deps) {
21912
22635
  }
21913
22636
 
21914
22637
  // src/routes/workspace-artifacts.ts
21915
- import { createHash as createHash8 } from "crypto";
22638
+ import { createHash as createHash9 } from "crypto";
21916
22639
  import {
21917
22640
  CreateWorkspaceArtifactRequest,
21918
22641
  PublishWorkspaceArtifactVersionRequest,
@@ -21981,7 +22704,7 @@ function errorResponse(context, error) {
21981
22704
  }
21982
22705
  function contentMetadata(workspaceId, html) {
21983
22706
  const bytes = encoder2.encode(html);
21984
- const sha256 = createHash8("sha256").update(bytes).digest("hex");
22707
+ const sha256 = createHash9("sha256").update(bytes).digest("hex");
21985
22708
  return {
21986
22709
  bytes,
21987
22710
  contentSha256: sha256,
@@ -22007,7 +22730,7 @@ function prepareHtml(deps, workspaceId, html) {
22007
22730
  }
22008
22731
  function provenance(subjectId, idempotencyKey) {
22009
22732
  return {
22010
- operationKey: `subject:${createHash8("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
22733
+ operationKey: `subject:${createHash9("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
22011
22734
  actorSubjectId: subjectId,
22012
22735
  sourceSessionId: null,
22013
22736
  sourceTurnId: null,
@@ -22098,7 +22821,7 @@ function registerWorkspaceArtifactRoutes(app, deps) {
22098
22821
  );
22099
22822
  const object5 = await deps.objectStorage.getObjectBytes(ref.contentKey);
22100
22823
  if (!object5) throw new HTTPException29(503, { message: "Artifact content is unavailable" });
22101
- const actualHash = createHash8("sha256").update(object5.bytes).digest("hex");
22824
+ const actualHash = createHash9("sha256").update(object5.bytes).digest("hex");
22102
22825
  if (actualHash !== ref.version.contentSha256) {
22103
22826
  throw new HTTPException29(503, { message: "Artifact content failed integrity verification" });
22104
22827
  }
@@ -22192,7 +22915,7 @@ import {
22192
22915
  import {
22193
22916
  hasPermission as hasPermission13,
22194
22917
  requireAccessGrant as requireAccessGrant20,
22195
- requireAccessGrantAuthorization
22918
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization2
22196
22919
  } from "@opengeni/core";
22197
22920
  import {
22198
22921
  activatePreferenceRegistryRevision,
@@ -22340,7 +23063,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22340
23063
  });
22341
23064
  app.post(`${base}/proposals`, async (context) => {
22342
23065
  const workspaceId = context.req.param("workspaceId");
22343
- const access = await requireAccessGrantAuthorization(
23066
+ const access = await requireAccessGrantAuthorization2(
22344
23067
  context,
22345
23068
  deps,
22346
23069
  workspaceId,
@@ -22420,7 +23143,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22420
23143
  });
22421
23144
  app.post(`${base}/:preferenceId/activate`, async (context) => {
22422
23145
  const workspaceId = context.req.param("workspaceId");
22423
- const access = await requireAccessGrantAuthorization(
23146
+ const access = await requireAccessGrantAuthorization2(
22424
23147
  context,
22425
23148
  deps,
22426
23149
  workspaceId,
@@ -22449,7 +23172,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22449
23172
  });
22450
23173
  app.post(`${base}/:preferenceId/correct`, async (context) => {
22451
23174
  const workspaceId = context.req.param("workspaceId");
22452
- const access = await requireAccessGrantAuthorization(
23175
+ const access = await requireAccessGrantAuthorization2(
22453
23176
  context,
22454
23177
  deps,
22455
23178
  workspaceId,
@@ -22478,7 +23201,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22478
23201
  });
22479
23202
  app.post(`${base}/:preferenceId/scope`, async (context) => {
22480
23203
  const workspaceId = context.req.param("workspaceId");
22481
- const access = await requireAccessGrantAuthorization(
23204
+ const access = await requireAccessGrantAuthorization2(
22482
23205
  context,
22483
23206
  deps,
22484
23207
  workspaceId,
@@ -22507,7 +23230,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22507
23230
  });
22508
23231
  app.post(`${base}/:preferenceId/deactivate`, async (context) => {
22509
23232
  const workspaceId = context.req.param("workspaceId");
22510
- const access = await requireAccessGrantAuthorization(
23233
+ const access = await requireAccessGrantAuthorization2(
22511
23234
  context,
22512
23235
  deps,
22513
23236
  workspaceId,
@@ -22536,7 +23259,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22536
23259
  });
22537
23260
  app.post(`${base}/:preferenceId/supersede`, async (context) => {
22538
23261
  const workspaceId = context.req.param("workspaceId");
22539
- const access = await requireAccessGrantAuthorization(
23262
+ const access = await requireAccessGrantAuthorization2(
22540
23263
  context,
22541
23264
  deps,
22542
23265
  workspaceId,
@@ -22565,7 +23288,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22565
23288
  });
22566
23289
  app.post(`${base}/:preferenceId/reject`, async (context) => {
22567
23290
  const workspaceId = context.req.param("workspaceId");
22568
- const access = await requireAccessGrantAuthorization(
23291
+ const access = await requireAccessGrantAuthorization2(
22569
23292
  context,
22570
23293
  deps,
22571
23294
  workspaceId,
@@ -23072,7 +23795,7 @@ async function firstAvailable(providers, context) {
23072
23795
  }
23073
23796
 
23074
23797
  // src/integrations/slack-interactions.ts
23075
- import { createHash as createHash9, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
23798
+ import { createHash as createHash10, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
23076
23799
  import {
23077
23800
  DEFAULT_FIRST_PARTY_MCP_TOOLS as DEFAULT_FIRST_PARTY_MCP_TOOLS2,
23078
23801
  hasOpenGeniSlackReactionScope,
@@ -23220,7 +23943,7 @@ function slackReactionInboxEntry(payload, bot, settings) {
23220
23943
  if (!settings.enabled || !event || event.type !== "reaction_added" || item?.type !== "message" || !teamId || !eventId || !userId || userId === bot.botUserId || !reaction || reaction !== settings.emoji || !channelId || !workspaceSlackReactionChannelAllowed(settings, channelId) || !timestamp) {
23221
23944
  return null;
23222
23945
  }
23223
- const stableReactionIdentity = createHash9("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
23946
+ const stableReactionIdentity = createHash10("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
23224
23947
  return {
23225
23948
  providerEventId: eventId,
23226
23949
  providerMessageId: `reaction:${stableReactionIdentity}`,
@@ -24221,7 +24944,7 @@ function slackRouteKey(channelId, threadTs) {
24221
24944
  return `${channelId}:${threadTs}`;
24222
24945
  }
24223
24946
  function deterministicUuid(value) {
24224
- const bytes = createHash9("sha256").update(value).digest().subarray(0, 16);
24947
+ const bytes = createHash10("sha256").update(value).digest().subarray(0, 16);
24225
24948
  bytes[6] = bytes[6] & 15 | 80;
24226
24949
  bytes[8] = bytes[8] & 63 | 128;
24227
24950
  const hex = bytes.toString("hex");
@@ -24358,14 +25081,27 @@ function createAppComposition(deps) {
24358
25081
  indexDocument: async ({
24359
25082
  accountId,
24360
25083
  workspaceId,
24361
- documentId
25084
+ documentId,
25085
+ authorityKind,
25086
+ authorityWorkspaceId,
25087
+ authoritySubjectId
24362
25088
  }) => {
24363
25089
  if (!objectStorage) {
24364
25090
  throw new HTTPException33(503, {
24365
25091
  message: "object storage is not configured"
24366
25092
  });
24367
25093
  }
24368
- return await indexDocumentNow(
25094
+ const context = await rlsContextForWorkspace(deps.db, workspaceId);
25095
+ if (context.accountId !== accountId) {
25096
+ throw new Error("document account/workspace authority mismatch");
25097
+ }
25098
+ const claimedDocument = await getDocument2(deps.db, workspaceId, documentId, {
25099
+ viewerSubjectId: authoritySubjectId
25100
+ });
25101
+ if (!claimedDocument || claimedDocument.authorityKind !== authorityKind || claimedDocument.authorityWorkspaceId !== authorityWorkspaceId || claimedDocument.authoritySubjectId !== authoritySubjectId) {
25102
+ throw new Error("document authority changed before indexing");
25103
+ }
25104
+ const document = await indexDocumentNow(
24369
25105
  deps.db,
24370
25106
  objectStorage,
24371
25107
  workspaceId,
@@ -24380,8 +25116,13 @@ function createAppComposition(deps) {
24380
25116
  quantity: chunkCount
24381
25117
  });
24382
25118
  }
24383
- }
25119
+ },
25120
+ { viewerSubjectId: authoritySubjectId }
24384
25121
  );
25122
+ if (document.authorityKind !== authorityKind || document.authorityWorkspaceId !== authorityWorkspaceId || document.authoritySubjectId !== authoritySubjectId) {
25123
+ throw new Error("document authority changed before indexing");
25124
+ }
25125
+ return document;
24385
25126
  }
24386
25127
  };
24387
25128
  const sandboxClient = deps.sandboxClient ?? createApiSandboxClient(deps.settings);
@@ -25349,4 +26090,4 @@ export {
25349
26090
  withDefaultEnabledCapabilityMcpTools,
25350
26091
  workflowIdForSession2 as workflowIdForSession
25351
26092
  };
25352
- //# sourceMappingURL=chunk-MWBF2GXL.js.map
26093
+ //# sourceMappingURL=chunk-5EEC7Q6C.js.map