@opengeni/api-router 0.17.0 → 0.20.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) {
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) {
11411
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,6 +21864,7 @@ 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,
@@ -21437,6 +21876,7 @@ import {
21437
21876
  WorkspaceInstructionPolicyDiffResponse,
21438
21877
  WorkspaceInstructionPolicyListQuery,
21439
21878
  WorkspaceInstructionPolicyListResponse,
21879
+ WorkspaceInstructionPolicyOperationReuseResponse,
21440
21880
  WorkspaceInstructionPolicyRevision
21441
21881
  } from "@opengeni/contracts";
21442
21882
  import { requireAccessGrant as requireAccessGrant17 } from "@opengeni/core";
@@ -21451,7 +21891,8 @@ import {
21451
21891
  WorkspaceInstructionPolicyConflictError,
21452
21892
  WorkspaceInstructionPolicyInvalidOperationError,
21453
21893
  WorkspaceInstructionPolicyLegacyUnavailableError,
21454
- WorkspaceInstructionPolicyNotFoundError
21894
+ WorkspaceInstructionPolicyNotFoundError,
21895
+ WorkspaceInstructionPolicyOperationReuseError
21455
21896
  } from "@opengeni/db";
21456
21897
  import { HTTPException as HTTPException27 } from "hono/http-exception";
21457
21898
  import { z as z7 } from "zod";
@@ -21474,6 +21915,15 @@ function policyErrorResponse(context, error) {
21474
21915
  409
21475
21916
  );
21476
21917
  }
21918
+ if (error instanceof WorkspaceInstructionPolicyOperationReuseError) {
21919
+ return context.json(
21920
+ WorkspaceInstructionPolicyOperationReuseResponse.parse({
21921
+ code: error.code,
21922
+ message: error.message
21923
+ }),
21924
+ 409
21925
+ );
21926
+ }
21477
21927
  if (error instanceof WorkspaceInstructionPolicyNotFoundError) {
21478
21928
  return context.json(
21479
21929
  { code: "WORKSPACE_INSTRUCTION_POLICY_NOT_FOUND", message: error.message },
@@ -21536,6 +21986,7 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21536
21986
  return context.json(
21537
21987
  WorkspaceInstructionPolicyRevision.parse(
21538
21988
  await createWorkspaceInstructionPolicyDraft(deps.db, {
21989
+ operationId: request.operationId ?? randomUUID(),
21539
21990
  accountId: grant.accountId,
21540
21991
  workspaceId,
21541
21992
  createdBySubjectId: grant.subjectId,
@@ -21563,6 +22014,7 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21563
22014
  return context.json(
21564
22015
  WorkspaceInstructionPolicyRevision.parse(
21565
22016
  await importLegacyWorkspaceInstructionPolicyDraft(deps.db, {
22017
+ operationId: request.operationId ?? randomUUID(),
21566
22018
  accountId: grant.accountId,
21567
22019
  workspaceId,
21568
22020
  createdBySubjectId: grant.subjectId,
@@ -21604,10 +22056,12 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21604
22056
  return context.json(
21605
22057
  WorkspaceInstructionPolicyActivationResponse.parse(
21606
22058
  await rollbackWorkspaceInstructionPolicyRevision(deps.db, {
22059
+ operationId: request.operationId ?? randomUUID(),
21607
22060
  accountId: grant.accountId,
21608
22061
  workspaceId,
21609
22062
  targetRevisionId: request.targetRevisionId,
21610
22063
  expectedCurrentRevisionId: request.expectedCurrentRevisionId,
22064
+ ...request.expectedActivationVersion === void 0 ? {} : { expectedActivationVersion: request.expectedActivationVersion },
21611
22065
  actorSubjectId: grant.subjectId,
21612
22066
  reason: request.reason
21613
22067
  })
@@ -21641,10 +22095,12 @@ function registerWorkspaceInstructionPolicyRoutes(app, deps) {
21641
22095
  return context.json(
21642
22096
  WorkspaceInstructionPolicyActivationResponse.parse(
21643
22097
  await activateWorkspaceInstructionPolicyRevision(deps.db, {
22098
+ operationId: request.operationId ?? randomUUID(),
21644
22099
  accountId: grant.accountId,
21645
22100
  workspaceId,
21646
22101
  revisionId,
21647
22102
  expectedCurrentRevisionId: request.expectedCurrentRevisionId,
22103
+ ...request.expectedActivationVersion === void 0 ? {} : { expectedActivationVersion: request.expectedActivationVersion },
21648
22104
  actorSubjectId: grant.subjectId,
21649
22105
  reason: request.reason
21650
22106
  })
@@ -21661,11 +22117,14 @@ import {
21661
22117
  WORKSPACE_STATE_MAX_BASES as WORKSPACE_STATE_MAX_BASES2,
21662
22118
  WORKSPACE_STATE_MAX_TOPICS as WORKSPACE_STATE_MAX_TOPICS2,
21663
22119
  WORKSPACE_STATE_TOPIC_MAX_CHARS as WORKSPACE_STATE_TOPIC_MAX_CHARS2,
22120
+ WorkspaceStateQuery,
21664
22121
  WorkspaceStateResponse as WorkspaceStateResponse2
21665
22122
  } from "@opengeni/contracts";
21666
22123
  import { hasPermission as hasPermission12, requireAccessGrant as requireAccessGrant18 } from "@opengeni/core";
21667
22124
  import {
21668
22125
  getWorkspace as getWorkspace2,
22126
+ getCurrentPreferenceRegistryGovernanceMetadata,
22127
+ getWorkspaceStateAcceptedAttemptGovernance,
21669
22128
  listWorkspaceStateMemoryRecords,
21670
22129
  listWorkspaceInstructionPolicyRevisions as listWorkspaceInstructionPolicyRevisions2
21671
22130
  } from "@opengeni/db";
@@ -21673,6 +22132,7 @@ import { getDocumentInventory } from "@opengeni/documents";
21673
22132
  import { HTTPException as HTTPException28 } from "hono/http-exception";
21674
22133
 
21675
22134
  // src/workspace-state-projection.ts
22135
+ import { createHash as createHash8 } from "crypto";
21676
22136
  import {
21677
22137
  KnowledgeMemoryKind,
21678
22138
  KnowledgeMemoryStatus,
@@ -21685,6 +22145,140 @@ import {
21685
22145
  WORKSPACE_STATE_TOPIC_MAX_CHARS,
21686
22146
  WorkspaceStateResponse
21687
22147
  } from "@opengeni/contracts";
22148
+ function hashIdentities(values) {
22149
+ return createHash8("sha256").update(values.join("\n"), "utf8").digest("hex");
22150
+ }
22151
+ function policyTargetKey(value) {
22152
+ return `${value.kind}:${value.scope}:${value.roleKey ?? ""}`;
22153
+ }
22154
+ function policyTargetKeysForRole(policyRole) {
22155
+ const keys = /* @__PURE__ */ new Set(["charter:global:", "policy:global:"]);
22156
+ if (policyRole !== null) keys.add(`policy:role:${policyRole}`);
22157
+ return keys;
22158
+ }
22159
+ function policyIdentity(value) {
22160
+ return `${policyTargetKey(value)}:${value.revisionId}:${value.contentHash}:${value.activationVersion}`;
22161
+ }
22162
+ function preferenceIdentity(value) {
22163
+ return `${value.scope}:${value.id}:${value.revisionId}:${value.contentHash}:${value.activeVersion}`;
22164
+ }
22165
+ function classifyIdentityDrift(snapshotIdentities, currentIdentities, snapshotKeys, currentKeys) {
22166
+ if (snapshotIdentities.join("\n") === currentIdentities.join("\n")) return "identical";
22167
+ return snapshotKeys.join("\n") === currentKeys.join("\n") ? "superseded" : "changed";
22168
+ }
22169
+ function overallDriftStatus(policy, preferences) {
22170
+ for (const status of ["unavailable", "truncated", "missing", "changed", "superseded"]) {
22171
+ if (policy === status || preferences === status) return status;
22172
+ }
22173
+ return "identical";
22174
+ }
22175
+ function attemptGovernanceProjection(input) {
22176
+ const governance = input.attemptGovernance ?? null;
22177
+ if (governance === null) return { status: "not_requested" };
22178
+ if (governance.status === "unavailable") {
22179
+ return {
22180
+ status: "unavailable",
22181
+ reason: "attempt_not_found_or_not_authorized",
22182
+ driftStatus: "unavailable"
22183
+ };
22184
+ }
22185
+ const policySnapshot = governance.policySnapshot;
22186
+ let policyStatus = "missing";
22187
+ let policySnapshotHash = null;
22188
+ let policyCurrentHash = null;
22189
+ let policySnapshotTargetCount = 0;
22190
+ let policyCurrentTargetCount = 0;
22191
+ if (policySnapshot) {
22192
+ const snapshotEntries = [...policySnapshot.entries].sort(
22193
+ (left, right) => policyTargetKey(left).localeCompare(policyTargetKey(right))
22194
+ );
22195
+ const snapshotKeys = snapshotEntries.map(policyTargetKey);
22196
+ const relevantTargetKeys = policyTargetKeysForRole(policySnapshot.policyRole);
22197
+ const currentEntries = input.policies.activeHeads.filter((head) => relevantTargetKeys.has(policyTargetKey(head))).sort((left, right) => policyTargetKey(left).localeCompare(policyTargetKey(right)));
22198
+ const snapshotIdentities = snapshotEntries.map(policyIdentity);
22199
+ const currentIdentities = currentEntries.map(policyIdentity);
22200
+ const currentKeys = currentEntries.map(policyTargetKey);
22201
+ policyStatus = classifyIdentityDrift(
22202
+ snapshotIdentities,
22203
+ currentIdentities,
22204
+ snapshotKeys,
22205
+ currentKeys
22206
+ );
22207
+ policySnapshotHash = hashIdentities(snapshotIdentities);
22208
+ policyCurrentHash = hashIdentities(currentIdentities);
22209
+ policySnapshotTargetCount = snapshotEntries.length;
22210
+ policyCurrentTargetCount = currentEntries.length;
22211
+ }
22212
+ const preferenceSnapshot = governance.preferenceSnapshot;
22213
+ const currentPreferences = [...governance.currentPreferences.descriptors].sort(
22214
+ (left, right) => preferenceIdentity(left).localeCompare(preferenceIdentity(right))
22215
+ );
22216
+ let preferenceStatus = "missing";
22217
+ let preferenceSnapshotHash = null;
22218
+ const currentPreferenceIdentities = currentPreferences.map(preferenceIdentity);
22219
+ const currentPreferenceHash = hashIdentities(currentPreferenceIdentities);
22220
+ let snapshotPreferenceCount = 0;
22221
+ let snapshotPreferenceTruncated = false;
22222
+ if (preferenceSnapshot) {
22223
+ const snapshotPreferences = [...preferenceSnapshot.descriptors].sort(
22224
+ (left, right) => preferenceIdentity(left).localeCompare(preferenceIdentity(right))
22225
+ );
22226
+ const snapshotPreferenceIdentities = snapshotPreferences.map(preferenceIdentity);
22227
+ const snapshotPreferenceKeys = snapshotPreferences.map((descriptor) => descriptor.id).sort();
22228
+ const currentPreferenceKeys = currentPreferences.map((descriptor) => descriptor.id).sort();
22229
+ preferenceSnapshotHash = hashIdentities(snapshotPreferenceIdentities);
22230
+ snapshotPreferenceCount = snapshotPreferences.length;
22231
+ snapshotPreferenceTruncated = preferenceSnapshot.truncated;
22232
+ preferenceStatus = preferenceSnapshot.truncated || governance.currentPreferences.truncated ? "truncated" : classifyIdentityDrift(
22233
+ snapshotPreferenceIdentities,
22234
+ currentPreferenceIdentities,
22235
+ snapshotPreferenceKeys,
22236
+ currentPreferenceKeys
22237
+ );
22238
+ }
22239
+ return {
22240
+ status: "available",
22241
+ attemptId: governance.attemptId,
22242
+ executionGeneration: governance.executionGeneration,
22243
+ acceptedAt: governance.acceptedAt,
22244
+ policySnapshot: policySnapshot ? {
22245
+ status: "available",
22246
+ id: policySnapshot.id,
22247
+ createdAt: policySnapshot.createdAt,
22248
+ entryHash: policySnapshot.entryHash,
22249
+ policyRole: policySnapshot.policyRole,
22250
+ roleSource: policySnapshot.roleSource,
22251
+ entries: policySnapshot.entries
22252
+ } : { status: "missing" },
22253
+ preferenceSnapshot: preferenceSnapshot ? {
22254
+ status: "available",
22255
+ id: preferenceSnapshot.id,
22256
+ createdAt: preferenceSnapshot.createdAt,
22257
+ descriptorHash: preferenceSnapshot.descriptorHash,
22258
+ descriptorCount: preferenceSnapshot.descriptors.length,
22259
+ truncated: preferenceSnapshot.truncated
22260
+ } : { status: "missing" },
22261
+ drift: {
22262
+ overall: overallDriftStatus(policyStatus, preferenceStatus),
22263
+ policy: {
22264
+ status: policyStatus,
22265
+ snapshotHash: policySnapshotHash,
22266
+ currentHash: policyCurrentHash,
22267
+ snapshotTargetCount: policySnapshotTargetCount,
22268
+ currentTargetCount: policyCurrentTargetCount
22269
+ },
22270
+ preferences: {
22271
+ status: preferenceStatus,
22272
+ snapshotHash: preferenceSnapshotHash,
22273
+ currentHash: currentPreferenceHash,
22274
+ snapshotDescriptorCount: snapshotPreferenceCount,
22275
+ currentDescriptorCount: currentPreferences.length,
22276
+ snapshotTruncated: snapshotPreferenceTruncated,
22277
+ currentTruncated: governance.currentPreferences.truncated
22278
+ }
22279
+ }
22280
+ };
22281
+ }
21688
22282
  function emptyMemoryStatusCounts() {
21689
22283
  return Object.fromEntries(
21690
22284
  KnowledgeMemoryStatus.options.map((status) => [status, 0])
@@ -21856,10 +22450,7 @@ function projectWorkspaceState(input) {
21856
22450
  generatedAt: input.generatedAt,
21857
22451
  truth: {
21858
22452
  current: { source: "read_time_projection", capturedAt: input.generatedAt },
21859
- policySnapshot: {
21860
- status: "not_captured",
21861
- reason: "workspace_instruction_policy_snapshot_not_implemented"
21862
- }
22453
+ attemptGovernance: attemptGovernanceProjection(input)
21863
22454
  },
21864
22455
  policy: policyProjection(input),
21865
22456
  knowledge: input.knowledge ? availableKnowledgeProjection(input.knowledge) : {
@@ -21874,10 +22465,11 @@ function projectWorkspaceState(input) {
21874
22465
  function registerWorkspaceStateRoutes(app, deps) {
21875
22466
  app.get("/v1/workspaces/:workspaceId/workspace-state", async (context) => {
21876
22467
  const workspaceId = context.req.param("workspaceId");
22468
+ const query = WorkspaceStateQuery.parse(context.req.query());
21877
22469
  const grant = await requireAccessGrant18(context, deps, workspaceId, "workspace:read");
21878
22470
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
21879
22471
  const canInspectKnowledge = hasPermission12(grant.permissions, "documents:search");
21880
- const [workspace, policies, knowledge] = await Promise.all([
22472
+ const [workspace, policies, knowledge, attemptGovernance] = await Promise.all([
21881
22473
  getWorkspace2(deps.db, workspaceId),
21882
22474
  listWorkspaceInstructionPolicyRevisions2(deps.db, workspaceId, { limit: 1 }),
21883
22475
  canInspectKnowledge ? (async () => {
@@ -21891,7 +22483,43 @@ function registerWorkspaceStateRoutes(app, deps) {
21891
22483
  listWorkspaceStateMemoryRecords(deps.db, workspaceId)
21892
22484
  ]);
21893
22485
  return { documents, memories };
21894
- })() : Promise.resolve(null)
22486
+ })() : Promise.resolve(null),
22487
+ query.attemptId ? getWorkspaceStateAcceptedAttemptGovernance(deps.db, {
22488
+ accountId: grant.accountId,
22489
+ workspaceId,
22490
+ subjectId: grant.subjectId,
22491
+ attemptId: query.attemptId
22492
+ }).then(async (snapshot) => {
22493
+ if (!snapshot) return { status: "unavailable" };
22494
+ const currentPreferences = await getCurrentPreferenceRegistryGovernanceMetadata(
22495
+ deps.db,
22496
+ {
22497
+ workspaceId,
22498
+ subjectId: grant.subjectId
22499
+ }
22500
+ );
22501
+ return {
22502
+ status: "available",
22503
+ attemptId: snapshot.attemptId,
22504
+ executionGeneration: snapshot.executionGeneration,
22505
+ acceptedAt: snapshot.acceptedAt,
22506
+ policySnapshot: snapshot.policySnapshot,
22507
+ preferenceSnapshot: snapshot.preferenceSnapshot ? {
22508
+ id: snapshot.preferenceSnapshot.id,
22509
+ descriptorHash: snapshot.preferenceSnapshot.descriptorHash,
22510
+ descriptors: snapshot.preferenceSnapshot.descriptors.map((descriptor) => ({
22511
+ id: descriptor.id,
22512
+ revisionId: descriptor.revisionId,
22513
+ contentHash: descriptor.contentHash,
22514
+ activeVersion: descriptor.activeVersion,
22515
+ scope: descriptor.scope
22516
+ })),
22517
+ truncated: snapshot.preferenceSnapshot.truncated,
22518
+ createdAt: snapshot.preferenceSnapshot.createdAt
22519
+ } : null,
22520
+ currentPreferences
22521
+ };
22522
+ }) : Promise.resolve(null)
21895
22523
  ]);
21896
22524
  if (!workspace) {
21897
22525
  throw new HTTPException28(404, { message: "workspace not found" });
@@ -21904,7 +22532,8 @@ function registerWorkspaceStateRoutes(app, deps) {
21904
22532
  generatedAt,
21905
22533
  workspaceAgentInstructions: workspace.agentInstructions,
21906
22534
  policies,
21907
- knowledge
22535
+ knowledge,
22536
+ attemptGovernance
21908
22537
  })
21909
22538
  )
21910
22539
  );
@@ -21912,7 +22541,7 @@ function registerWorkspaceStateRoutes(app, deps) {
21912
22541
  }
21913
22542
 
21914
22543
  // src/routes/workspace-artifacts.ts
21915
- import { createHash as createHash8 } from "crypto";
22544
+ import { createHash as createHash9 } from "crypto";
21916
22545
  import {
21917
22546
  CreateWorkspaceArtifactRequest,
21918
22547
  PublishWorkspaceArtifactVersionRequest,
@@ -21981,7 +22610,7 @@ function errorResponse(context, error) {
21981
22610
  }
21982
22611
  function contentMetadata(workspaceId, html) {
21983
22612
  const bytes = encoder2.encode(html);
21984
- const sha256 = createHash8("sha256").update(bytes).digest("hex");
22613
+ const sha256 = createHash9("sha256").update(bytes).digest("hex");
21985
22614
  return {
21986
22615
  bytes,
21987
22616
  contentSha256: sha256,
@@ -22007,7 +22636,7 @@ function prepareHtml(deps, workspaceId, html) {
22007
22636
  }
22008
22637
  function provenance(subjectId, idempotencyKey) {
22009
22638
  return {
22010
- operationKey: `subject:${createHash8("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
22639
+ operationKey: `subject:${createHash9("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
22011
22640
  actorSubjectId: subjectId,
22012
22641
  sourceSessionId: null,
22013
22642
  sourceTurnId: null,
@@ -22098,7 +22727,7 @@ function registerWorkspaceArtifactRoutes(app, deps) {
22098
22727
  );
22099
22728
  const object5 = await deps.objectStorage.getObjectBytes(ref.contentKey);
22100
22729
  if (!object5) throw new HTTPException29(503, { message: "Artifact content is unavailable" });
22101
- const actualHash = createHash8("sha256").update(object5.bytes).digest("hex");
22730
+ const actualHash = createHash9("sha256").update(object5.bytes).digest("hex");
22102
22731
  if (actualHash !== ref.version.contentSha256) {
22103
22732
  throw new HTTPException29(503, { message: "Artifact content failed integrity verification" });
22104
22733
  }
@@ -22192,7 +22821,7 @@ import {
22192
22821
  import {
22193
22822
  hasPermission as hasPermission13,
22194
22823
  requireAccessGrant as requireAccessGrant20,
22195
- requireAccessGrantAuthorization
22824
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization2
22196
22825
  } from "@opengeni/core";
22197
22826
  import {
22198
22827
  activatePreferenceRegistryRevision,
@@ -22340,7 +22969,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22340
22969
  });
22341
22970
  app.post(`${base}/proposals`, async (context) => {
22342
22971
  const workspaceId = context.req.param("workspaceId");
22343
- const access = await requireAccessGrantAuthorization(
22972
+ const access = await requireAccessGrantAuthorization2(
22344
22973
  context,
22345
22974
  deps,
22346
22975
  workspaceId,
@@ -22420,7 +23049,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22420
23049
  });
22421
23050
  app.post(`${base}/:preferenceId/activate`, async (context) => {
22422
23051
  const workspaceId = context.req.param("workspaceId");
22423
- const access = await requireAccessGrantAuthorization(
23052
+ const access = await requireAccessGrantAuthorization2(
22424
23053
  context,
22425
23054
  deps,
22426
23055
  workspaceId,
@@ -22449,7 +23078,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22449
23078
  });
22450
23079
  app.post(`${base}/:preferenceId/correct`, async (context) => {
22451
23080
  const workspaceId = context.req.param("workspaceId");
22452
- const access = await requireAccessGrantAuthorization(
23081
+ const access = await requireAccessGrantAuthorization2(
22453
23082
  context,
22454
23083
  deps,
22455
23084
  workspaceId,
@@ -22478,7 +23107,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22478
23107
  });
22479
23108
  app.post(`${base}/:preferenceId/scope`, async (context) => {
22480
23109
  const workspaceId = context.req.param("workspaceId");
22481
- const access = await requireAccessGrantAuthorization(
23110
+ const access = await requireAccessGrantAuthorization2(
22482
23111
  context,
22483
23112
  deps,
22484
23113
  workspaceId,
@@ -22507,7 +23136,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22507
23136
  });
22508
23137
  app.post(`${base}/:preferenceId/deactivate`, async (context) => {
22509
23138
  const workspaceId = context.req.param("workspaceId");
22510
- const access = await requireAccessGrantAuthorization(
23139
+ const access = await requireAccessGrantAuthorization2(
22511
23140
  context,
22512
23141
  deps,
22513
23142
  workspaceId,
@@ -22536,7 +23165,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22536
23165
  });
22537
23166
  app.post(`${base}/:preferenceId/supersede`, async (context) => {
22538
23167
  const workspaceId = context.req.param("workspaceId");
22539
- const access = await requireAccessGrantAuthorization(
23168
+ const access = await requireAccessGrantAuthorization2(
22540
23169
  context,
22541
23170
  deps,
22542
23171
  workspaceId,
@@ -22565,7 +23194,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
22565
23194
  });
22566
23195
  app.post(`${base}/:preferenceId/reject`, async (context) => {
22567
23196
  const workspaceId = context.req.param("workspaceId");
22568
- const access = await requireAccessGrantAuthorization(
23197
+ const access = await requireAccessGrantAuthorization2(
22569
23198
  context,
22570
23199
  deps,
22571
23200
  workspaceId,
@@ -23072,7 +23701,7 @@ async function firstAvailable(providers, context) {
23072
23701
  }
23073
23702
 
23074
23703
  // src/integrations/slack-interactions.ts
23075
- import { createHash as createHash9, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
23704
+ import { createHash as createHash10, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
23076
23705
  import {
23077
23706
  DEFAULT_FIRST_PARTY_MCP_TOOLS as DEFAULT_FIRST_PARTY_MCP_TOOLS2,
23078
23707
  hasOpenGeniSlackReactionScope,
@@ -23220,7 +23849,7 @@ function slackReactionInboxEntry(payload, bot, settings) {
23220
23849
  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
23850
  return null;
23222
23851
  }
23223
- const stableReactionIdentity = createHash9("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
23852
+ const stableReactionIdentity = createHash10("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
23224
23853
  return {
23225
23854
  providerEventId: eventId,
23226
23855
  providerMessageId: `reaction:${stableReactionIdentity}`,
@@ -24221,7 +24850,7 @@ function slackRouteKey(channelId, threadTs) {
24221
24850
  return `${channelId}:${threadTs}`;
24222
24851
  }
24223
24852
  function deterministicUuid(value) {
24224
- const bytes = createHash9("sha256").update(value).digest().subarray(0, 16);
24853
+ const bytes = createHash10("sha256").update(value).digest().subarray(0, 16);
24225
24854
  bytes[6] = bytes[6] & 15 | 80;
24226
24855
  bytes[8] = bytes[8] & 63 | 128;
24227
24856
  const hex = bytes.toString("hex");
@@ -24358,14 +24987,27 @@ function createAppComposition(deps) {
24358
24987
  indexDocument: async ({
24359
24988
  accountId,
24360
24989
  workspaceId,
24361
- documentId
24990
+ documentId,
24991
+ authorityKind,
24992
+ authorityWorkspaceId,
24993
+ authoritySubjectId
24362
24994
  }) => {
24363
24995
  if (!objectStorage) {
24364
24996
  throw new HTTPException33(503, {
24365
24997
  message: "object storage is not configured"
24366
24998
  });
24367
24999
  }
24368
- return await indexDocumentNow(
25000
+ const context = await rlsContextForWorkspace(deps.db, workspaceId);
25001
+ if (context.accountId !== accountId) {
25002
+ throw new Error("document account/workspace authority mismatch");
25003
+ }
25004
+ const claimedDocument = await getDocument2(deps.db, workspaceId, documentId, {
25005
+ viewerSubjectId: authoritySubjectId
25006
+ });
25007
+ if (!claimedDocument || claimedDocument.authorityKind !== authorityKind || claimedDocument.authorityWorkspaceId !== authorityWorkspaceId || claimedDocument.authoritySubjectId !== authoritySubjectId) {
25008
+ throw new Error("document authority changed before indexing");
25009
+ }
25010
+ const document = await indexDocumentNow(
24369
25011
  deps.db,
24370
25012
  objectStorage,
24371
25013
  workspaceId,
@@ -24380,8 +25022,13 @@ function createAppComposition(deps) {
24380
25022
  quantity: chunkCount
24381
25023
  });
24382
25024
  }
24383
- }
25025
+ },
25026
+ { viewerSubjectId: authoritySubjectId }
24384
25027
  );
25028
+ if (document.authorityKind !== authorityKind || document.authorityWorkspaceId !== authorityWorkspaceId || document.authoritySubjectId !== authoritySubjectId) {
25029
+ throw new Error("document authority changed before indexing");
25030
+ }
25031
+ return document;
24385
25032
  }
24386
25033
  };
24387
25034
  const sandboxClient = deps.sandboxClient ?? createApiSandboxClient(deps.settings);
@@ -25349,4 +25996,4 @@ export {
25349
25996
  withDefaultEnabledCapabilityMcpTools,
25350
25997
  workflowIdForSession2 as workflowIdForSession
25351
25998
  };
25352
- //# sourceMappingURL=chunk-MWBF2GXL.js.map
25999
+ //# sourceMappingURL=chunk-AEVD7E2F.js.map