@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.
- package/dist/app.js +1 -1
- package/dist/{chunk-MWBF2GXL.js → chunk-5EEC7Q6C.js} +852 -111
- package/dist/chunk-5EEC7Q6C.js.map +1 -0
- package/dist/codex-realtime.d.ts +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-drive.d.ts +16 -1
- package/dist/mcp/documents.d.ts +3 -3
- package/dist/workspace-state-projection.d.ts +29 -1
- package/package.json +11 -11
- package/src/app.ts +33 -2
- package/src/codex-realtime.ts +4 -0
- package/src/index.ts +3 -2
- package/src/integrations/google-drive.ts +385 -20
- package/src/mcp/documents.ts +49 -27
- package/src/routes/connections.ts +61 -9
- package/src/routes/documents.ts +134 -43
- package/src/routes/workspace-instruction-policies.ts +118 -0
- package/src/routes/workspace-state.ts +46 -1
- package/src/workspace-state-projection.ts +203 -4
- package/dist/chunk-MWBF2GXL.js.map +0 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
13153
|
-
|
|
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
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
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
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:${
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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-
|
|
26093
|
+
//# sourceMappingURL=chunk-5EEC7Q6C.js.map
|