@opengeni/documents 0.3.3 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/documents",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@llamaindex/liteparse": "^1.5.3",
39
- "@opengeni/config": "^0.10.7",
40
- "@opengeni/contracts": "^0.36.0",
41
- "@opengeni/db": "^0.27.0",
42
- "@opengeni/storage": "^0.2.60",
39
+ "@opengeni/config": "^0.10.9",
40
+ "@opengeni/contracts": "^0.37.0",
41
+ "@opengeni/db": "^0.27.3",
42
+ "@opengeni/storage": "^0.2.62",
43
43
  "drizzle-orm": "^0.45.2",
44
44
  "openai": "6.36.0"
45
45
  }
@@ -1,8 +1,10 @@
1
1
  import type { ScopedKnowledgeScope } from "@opengeni/contracts";
2
- import type {
3
- GoogleDriveSelectedSource,
4
- GoogleDriveTargetScope,
5
- } from "@opengeni/contracts/google-drive";
2
+ import {
3
+ connectorDestinationDocumentAuthority,
4
+ resolveConnectorDocumentDestination,
5
+ type ConnectorDocumentDestination,
6
+ } from "@opengeni/contracts/connector-destinations";
7
+ import type { GoogleDriveSelectedSource } from "@opengeni/contracts/google-drive";
6
8
 
7
9
  export const GOOGLE_DRIVE_PROVIDER_KEY = "google-drive" as const;
8
10
  export const GOOGLE_DRIVE_FOLDER_MIME_TYPE = "application/vnd.google-apps.folder" as const;
@@ -232,36 +234,52 @@ export class GoogleDriveInventoryProviderError extends Error {
232
234
  }
233
235
 
234
236
  export function googleDriveKnowledgeScope(
235
- targetScope: GoogleDriveTargetScope,
236
- workspaceId: string,
237
- initiatingSubjectId: string,
237
+ destination: ConnectorDocumentDestination,
238
238
  ): ScopedKnowledgeScope {
239
- const boundedWorkspaceId = boundedText(workspaceId, "workspaceId", 256);
240
- const boundedSubjectId = boundedText(initiatingSubjectId, "initiatingSubjectId", 1024);
241
- if (targetScope === "organization") {
239
+ const authority = connectorDestinationDocumentAuthority(destination);
240
+ if (authority.authorityKind === "organization") {
242
241
  return { kind: "organization", workspaceId: null, subjectId: null };
243
242
  }
244
- if (targetScope === "workspace") {
245
- return { kind: "workspace", workspaceId: boundedWorkspaceId, subjectId: null };
243
+ if (authority.authorityKind === "workspace") {
244
+ if (!authority.authorityWorkspaceId) {
245
+ throw new Error("workspace connector destination is missing workspace authority");
246
+ }
247
+ return {
248
+ kind: "workspace",
249
+ workspaceId: authority.authorityWorkspaceId,
250
+ subjectId: null,
251
+ };
252
+ }
253
+ if (!authority.authorityWorkspaceId || !authority.authoritySubjectId) {
254
+ throw new Error("personal connector destination is missing immutable authority");
246
255
  }
247
- // Google Drive connections are workspace-bound today. Preserve that boundary
248
- // for personal knowledge rather than silently widening it across workspaces.
249
256
  return {
250
257
  kind: "personal",
251
- workspaceId: boundedWorkspaceId,
252
- subjectId: boundedSubjectId,
258
+ workspaceId: authority.authorityWorkspaceId,
259
+ subjectId: authority.authoritySubjectId,
253
260
  };
254
261
  }
255
262
 
256
263
  export function googleDriveKnowledgeSourceIdentity(input: {
257
264
  googlePermissionId: string;
258
- source: Pick<GoogleDriveSelectedSource, "id" | "driveId" | "targetScope">;
265
+ source: Pick<GoogleDriveSelectedSource, "id" | "driveId" | "destination" | "targetScope">;
266
+ accountId?: string | undefined;
259
267
  workspaceId: string;
260
- initiatingSubjectId: string;
268
+ connectionSubjectId?: string | null | undefined;
269
+ /** @deprecated Used only to validate old callers while source destinations migrate. */
270
+ initiatingSubjectId?: string | undefined;
261
271
  }): GoogleDriveKnowledgeSourceIdentity {
262
272
  const sourceId = driveId(input.source.id, "source.id");
263
273
  const externalTenantId = normalizedGooglePermissionId(input.googlePermissionId);
264
274
  const sourceDriveId = nullableDriveId(input.source.driveId, "source.driveId");
275
+ const destination = resolveConnectorDocumentDestination(input.source.destination, {
276
+ accountId: input.accountId ?? input.workspaceId,
277
+ workspaceId: input.workspaceId,
278
+ connectionSubjectId:
279
+ input.connectionSubjectId !== undefined
280
+ ? input.connectionSubjectId
281
+ : (input.initiatingSubjectId ?? null),
282
+ });
265
283
  return {
266
284
  providerKey: GOOGLE_DRIVE_PROVIDER_KEY,
267
285
  externalTenantId,
@@ -273,11 +291,7 @@ export function googleDriveKnowledgeSourceIdentity(input: {
273
291
  ? "google-drive-shared-drive"
274
292
  : "google-drive-folder",
275
293
  sourceUri: googleDriveSourceUri(sourceId),
276
- scope: googleDriveKnowledgeScope(
277
- input.source.targetScope,
278
- input.workspaceId,
279
- input.initiatingSubjectId,
280
- ),
294
+ scope: googleDriveKnowledgeScope(destination),
281
295
  };
282
296
  }
283
297
 
@@ -327,8 +341,11 @@ export function planGoogleDriveTransfer(
327
341
  export async function inventoryGoogleDriveSource(input: {
328
342
  googlePermissionId: string;
329
343
  source: GoogleDriveSelectedSource;
344
+ accountId?: string | undefined;
330
345
  workspaceId: string;
331
- initiatingSubjectId: string;
346
+ connectionSubjectId?: string | null | undefined;
347
+ /** @deprecated Used only to validate old callers while source destinations migrate. */
348
+ initiatingSubjectId?: string | undefined;
332
349
  limits: GoogleDriveInventoryLimits;
333
350
  listChildren: GoogleDriveListChildren;
334
351
  checkpoint?: GoogleDriveInventoryCheckpoint | null;
@@ -339,8 +356,12 @@ export async function inventoryGoogleDriveSource(input: {
339
356
  const source = googleDriveKnowledgeSourceIdentity({
340
357
  googlePermissionId,
341
358
  source: input.source,
359
+ ...(input.accountId ? { accountId: input.accountId } : {}),
342
360
  workspaceId: input.workspaceId,
343
- initiatingSubjectId: input.initiatingSubjectId,
361
+ ...(input.connectionSubjectId !== undefined
362
+ ? { connectionSubjectId: input.connectionSubjectId }
363
+ : {}),
364
+ ...(input.initiatingSubjectId ? { initiatingSubjectId: input.initiatingSubjectId } : {}),
344
365
  });
345
366
  const checkpointIdentity: GoogleDriveInventoryCheckpointIdentity = {
346
367
  googlePermissionId,
package/src/index.ts CHANGED
@@ -927,7 +927,7 @@ export async function addDocumentToBase(
927
927
  )
928
928
  .limit(1);
929
929
  if (existing) {
930
- if (!documentMatchesAccess(existing, input.access)) {
930
+ if (!documentMatchesAccess(existing, input.workspaceId, input.access)) {
931
931
  throw new Error(`Document not found: ${existing.id}`);
932
932
  }
933
933
  assertOrganizationDocumentAuthority(
@@ -1878,28 +1878,64 @@ function documentAccessConditions(
1878
1878
  }
1879
1879
 
1880
1880
  function documentMatchesAccess(
1881
- document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId" | "agentAccess">,
1881
+ document: Pick<
1882
+ DocumentAccessRecord,
1883
+ "authorityKind" | "authorityWorkspaceId" | "authoritySubjectId" | "agentAccess"
1884
+ >,
1885
+ workspaceId: string,
1882
1886
  access: DocumentAccessFilter | undefined,
1883
1887
  ): boolean {
1884
1888
  if (access?.agentOnly) {
1885
- return document.agentAccess && canViewDocument(document, access.viewerSubjectId);
1889
+ return document.agentAccess && canViewDocument(document, access.viewerSubjectId, workspaceId);
1890
+ }
1891
+ return canViewDocument(document, access?.viewerSubjectId, workspaceId);
1892
+ }
1893
+
1894
+ function canonicalDocumentAuthoritySubject(value: unknown): string | undefined {
1895
+ if (typeof value !== "string") return undefined;
1896
+ const subjectId = cleanString(value);
1897
+ if (!subjectId || subjectId !== value) return undefined;
1898
+ if (new TextEncoder().encode(subjectId).byteLength > DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES) {
1899
+ return undefined;
1886
1900
  }
1887
- return canViewDocument(document, access?.viewerSubjectId);
1901
+ return subjectId;
1888
1902
  }
1889
1903
 
1890
- /** Whether a single already-fetched document is readable by this human viewer. */
1904
+ /**
1905
+ * Whether a single already-fetched document is readable in this workspace.
1906
+ *
1907
+ * Keep this compatibility predicate as strict as SQL/RLS: personal authority
1908
+ * remains anchored to its originating workspace, and unknown or incomplete
1909
+ * authority tuples deny instead of falling through as workspace-visible.
1910
+ */
1891
1911
  export function canViewDocument(
1892
- document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId">,
1912
+ document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId"> & {
1913
+ authorityWorkspaceId?: string | null | undefined;
1914
+ },
1893
1915
  viewerSubjectId: string | null | undefined,
1916
+ workspaceId?: string | null | undefined,
1894
1917
  ): boolean {
1895
- return (
1896
- document.authorityKind !== "personal" ||
1897
- (!!viewerSubjectId && document.authoritySubjectId === viewerSubjectId)
1898
- );
1918
+ if (document.authorityKind === "organization") {
1919
+ return document.authorityWorkspaceId === null && document.authoritySubjectId === null;
1920
+ }
1921
+ const normalizedWorkspaceId = cleanString(workspaceId ?? null);
1922
+ if (!normalizedWorkspaceId || document.authorityWorkspaceId !== normalizedWorkspaceId) {
1923
+ return false;
1924
+ }
1925
+ if (document.authorityKind === "workspace") {
1926
+ return document.authoritySubjectId === null;
1927
+ }
1928
+ if (document.authorityKind === "personal") {
1929
+ const authoritySubjectId = canonicalDocumentAuthoritySubject(document.authoritySubjectId);
1930
+ const viewer = canonicalDocumentAuthoritySubject(viewerSubjectId);
1931
+ return !!authoritySubjectId && authoritySubjectId === viewer;
1932
+ }
1933
+ return false;
1899
1934
  }
1900
1935
 
1901
1936
  type DocumentAccessRecord = {
1902
1937
  authorityKind: string;
1938
+ authorityWorkspaceId: string | null;
1903
1939
  authoritySubjectId: string | null;
1904
1940
  agentAccess: boolean;
1905
1941
  };