@opengeni/documents 0.3.3 → 0.5.3

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.5.3",
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.11",
40
+ "@opengeni/contracts": "^0.38.1",
41
+ "@opengeni/db": "^0.27.7",
42
+ "@opengeni/storage": "^0.2.64",
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
@@ -136,6 +136,7 @@ export type DocumentAuthority = {
136
136
 
137
137
  export type DocumentInventoryStatusCounts = Record<DocumentStatus, number>;
138
138
  export type DocumentInventorySourceKindCounts = Record<KnowledgeSourceKind, number>;
139
+ export type DocumentInventoryAuthorityKindCounts = Record<DocumentAuthorityKind, number>;
139
140
 
140
141
  export type DocumentInventory = {
141
142
  baseCount: number;
@@ -149,6 +150,7 @@ export type DocumentInventory = {
149
150
  visibleDocumentCount: number;
150
151
  statusCounts: DocumentInventoryStatusCounts;
151
152
  sourceKindCounts: DocumentInventorySourceKindCounts;
153
+ authorityKindCounts: DocumentInventoryAuthorityKindCounts;
152
154
  latestUpdatedAt: string | null;
153
155
  topics: Array<{ name: string; documentCount: number }>;
154
156
  topicsTruncated: boolean;
@@ -709,6 +711,9 @@ export async function getDocumentInventory(
709
711
  documentCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'document')::int`,
710
712
  webCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'web')::int`,
711
713
  otherCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'other')::int`,
714
+ organizationAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'organization')::int`,
715
+ workspaceAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'workspace')::int`,
716
+ personalAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'personal')::int`,
712
717
  latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,
713
718
  })
714
719
  .from(schema.documents)
@@ -762,6 +767,11 @@ export async function getDocumentInventory(
762
767
  web: Number(summary?.webCount ?? 0),
763
768
  other: Number(summary?.otherCount ?? 0),
764
769
  },
770
+ authorityKindCounts: {
771
+ organization: Number(summary?.organizationAuthorityCount ?? 0),
772
+ workspace: Number(summary?.workspaceAuthorityCount ?? 0),
773
+ personal: Number(summary?.personalAuthorityCount ?? 0),
774
+ },
765
775
  latestUpdatedAt: documentInventoryTimestamp(summary?.latestUpdatedAt ?? null),
766
776
  topics,
767
777
  topicsTruncated: topicRows.length > topics.length,
@@ -927,7 +937,7 @@ export async function addDocumentToBase(
927
937
  )
928
938
  .limit(1);
929
939
  if (existing) {
930
- if (!documentMatchesAccess(existing, input.access)) {
940
+ if (!documentMatchesAccess(existing, input.workspaceId, input.access)) {
931
941
  throw new Error(`Document not found: ${existing.id}`);
932
942
  }
933
943
  assertOrganizationDocumentAuthority(
@@ -1878,28 +1888,64 @@ function documentAccessConditions(
1878
1888
  }
1879
1889
 
1880
1890
  function documentMatchesAccess(
1881
- document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId" | "agentAccess">,
1891
+ document: Pick<
1892
+ DocumentAccessRecord,
1893
+ "authorityKind" | "authorityWorkspaceId" | "authoritySubjectId" | "agentAccess"
1894
+ >,
1895
+ workspaceId: string,
1882
1896
  access: DocumentAccessFilter | undefined,
1883
1897
  ): boolean {
1884
1898
  if (access?.agentOnly) {
1885
- return document.agentAccess && canViewDocument(document, access.viewerSubjectId);
1899
+ return document.agentAccess && canViewDocument(document, access.viewerSubjectId, workspaceId);
1886
1900
  }
1887
- return canViewDocument(document, access?.viewerSubjectId);
1901
+ return canViewDocument(document, access?.viewerSubjectId, workspaceId);
1888
1902
  }
1889
1903
 
1890
- /** Whether a single already-fetched document is readable by this human viewer. */
1904
+ function canonicalDocumentAuthoritySubject(value: unknown): string | undefined {
1905
+ if (typeof value !== "string") return undefined;
1906
+ const subjectId = cleanString(value);
1907
+ if (!subjectId || subjectId !== value) return undefined;
1908
+ if (new TextEncoder().encode(subjectId).byteLength > DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES) {
1909
+ return undefined;
1910
+ }
1911
+ return subjectId;
1912
+ }
1913
+
1914
+ /**
1915
+ * Whether a single already-fetched document is readable in this workspace.
1916
+ *
1917
+ * Keep this compatibility predicate as strict as SQL/RLS: personal authority
1918
+ * remains anchored to its originating workspace, and unknown or incomplete
1919
+ * authority tuples deny instead of falling through as workspace-visible.
1920
+ */
1891
1921
  export function canViewDocument(
1892
- document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId">,
1922
+ document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId"> & {
1923
+ authorityWorkspaceId?: string | null | undefined;
1924
+ },
1893
1925
  viewerSubjectId: string | null | undefined,
1926
+ workspaceId?: string | null | undefined,
1894
1927
  ): boolean {
1895
- return (
1896
- document.authorityKind !== "personal" ||
1897
- (!!viewerSubjectId && document.authoritySubjectId === viewerSubjectId)
1898
- );
1928
+ if (document.authorityKind === "organization") {
1929
+ return document.authorityWorkspaceId === null && document.authoritySubjectId === null;
1930
+ }
1931
+ const normalizedWorkspaceId = cleanString(workspaceId ?? null);
1932
+ if (!normalizedWorkspaceId || document.authorityWorkspaceId !== normalizedWorkspaceId) {
1933
+ return false;
1934
+ }
1935
+ if (document.authorityKind === "workspace") {
1936
+ return document.authoritySubjectId === null;
1937
+ }
1938
+ if (document.authorityKind === "personal") {
1939
+ const authoritySubjectId = canonicalDocumentAuthoritySubject(document.authoritySubjectId);
1940
+ const viewer = canonicalDocumentAuthoritySubject(viewerSubjectId);
1941
+ return !!authoritySubjectId && authoritySubjectId === viewer;
1942
+ }
1943
+ return false;
1899
1944
  }
1900
1945
 
1901
1946
  type DocumentAccessRecord = {
1902
1947
  authorityKind: string;
1948
+ authorityWorkspaceId: string | null;
1903
1949
  authoritySubjectId: string | null;
1904
1950
  agentAccess: boolean;
1905
1951
  };