@opengeni/documents 0.5.38 → 0.5.41

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Settings } from "@opengeni/config";
2
- import type { AddDocumentRequest, CreateDocumentBaseRequest, Document, DocumentAuthorityKind, DocumentBase, DocumentCurationStatus, DocumentSearchMode, DocumentSearchResult, DocumentStatus, DocumentVisibility, FileAsset, KnowledgeBrowseResponse, KnowledgeRecord, KnowledgeSearchResponse, KnowledgeSourceKind, ListIndexedDocumentsResponse } from "@opengeni/contracts";
2
+ import { type AddDocumentRequest, CreateDocumentBaseRequest, Document, DocumentAuthorityKind, DocumentBase, DocumentCurationStatus, DocumentSearchMode, DocumentSearchResult, DocumentStatus, DocumentVisibility, FileAsset, KnowledgeBrowseResponse, KnowledgeRecord, KnowledgeSearchResponse, KnowledgeSourceKind, ListIndexedDocumentsResponse } from "@opengeni/contracts";
3
3
  import { type Database } from "@opengeni/db";
4
4
  import type { ObjectStorage } from "@opengeni/storage";
5
5
  export { projectKnowledgeRecord } from "./knowledge-projection.js";
@@ -327,6 +327,14 @@ export declare function decodeDocumentIndexCheckpoint(value: string, scope: {
327
327
  initiatingSubjectId: string;
328
328
  }): bigint;
329
329
  export declare function getDocument(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter): Promise<Document | null>;
330
+ /**
331
+ * Internal ingestion-only read used after the worker has independently resolved
332
+ * and fenced the immutable document authority tuple. It deliberately does not
333
+ * apply provider retrieval authorization because a new Drive document must be
334
+ * indexed before its first ACL evidence can be attached. User, API, MCP, and
335
+ * agent reads must use getDocument/effective retrieval instead.
336
+ */
337
+ export declare function getDocumentForIndexing(db: Database, workspaceId: string, documentId: string): Promise<Document | null>;
330
338
  export declare function queueDocumentForReindex(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter, organizationAuthorityGranted?: boolean): Promise<Document>;
331
339
  export declare function indexDocumentNow(db: Database, objectStorage: ObjectStorage, workspaceId: string, documentId: string, services?: DocumentServices, hooks?: DocumentIndexHooks, access?: DocumentAccessFilter): Promise<Document>;
332
340
  export declare function searchDocuments(db: Database, input: DocumentSearchInput, services?: Pick<DocumentServices, "embedder">): Promise<DocumentSearchResult[]>;
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // src/index.ts
2
2
  import {
3
- requireFile,
3
+ KnowledgeProviderCitation
4
+ } from "@opengeni/contracts";
5
+ import {
6
+ getFilesForSubject,
4
7
  rlsContextForWorkspace,
5
8
  setSubjectRlsContext,
6
9
  withRlsContext,
@@ -715,7 +718,18 @@ async function addDocumentToBase(db, input) {
715
718
  if (viewerSubjectId) await setSubjectRlsContext(scopedDb, viewerSubjectId);
716
719
  const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);
717
720
  if (!base) throw new Error(`Document base not found: ${input.baseId}`);
718
- const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);
721
+ const initiatingSubjectId = cleanString(input.initiatingSubjectId ?? null);
722
+ const createdBy = cleanString(input.createdBy ?? null);
723
+ if (initiatingSubjectId && createdBy && initiatingSubjectId !== createdBy) {
724
+ throw new Error("document file authority must match the exact initiating subject");
725
+ }
726
+ const fileAuthoritySubjectId = initiatingSubjectId ?? createdBy ?? null;
727
+ const file = await requireReadyFile(scopedDb, {
728
+ accountId: input.accountId,
729
+ workspaceId: input.workspaceId,
730
+ subjectId: fileAuthoritySubjectId,
731
+ fileId: input.fileId
732
+ });
719
733
  const knowledgeSourceIdentity = cleanString(input.knowledgeSourceIdentity ?? null);
720
734
  if (knowledgeSourceIdentity && knowledgeSourceIdentity.length > 512) {
721
735
  throw new Error("knowledge source document identity exceeds 512 characters");
@@ -785,7 +799,7 @@ async function addDocumentToBase(db, input) {
785
799
  authoritySubjectId: authority.subjectId,
786
800
  visibility: authority.kind === "personal" ? "private" : "workspace",
787
801
  agentAccess: input.agentAccess ?? true,
788
- createdBy: input.createdBy ?? null,
802
+ createdBy: fileAuthoritySubjectId,
789
803
  curationStatus: input.curationStatus ?? "none",
790
804
  updatedAt: now
791
805
  }).returning();
@@ -1009,6 +1023,14 @@ async function getDocument(db, workspaceId, documentId, access) {
1009
1023
  return row ? mapDocument(row) : null;
1010
1024
  });
1011
1025
  }
1026
+ async function getDocumentForIndexing(db, workspaceId, documentId) {
1027
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
1028
+ const [row] = await scopedDb.select().from(schema.documents).where(
1029
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
1030
+ ).limit(1);
1031
+ return row ? mapDocument(row) : null;
1032
+ });
1033
+ }
1012
1034
  async function queueDocumentForReindex(db, workspaceId, documentId, access, organizationAuthorityGranted) {
1013
1035
  return await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
1014
1036
  const [document] = await scopedDb.select({ authorityKind: schema.documents.authorityKind }).from(schema.documents).where(
@@ -1051,7 +1073,12 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
1051
1073
  );
1052
1074
  if (!loadedDocument) throw new Error(`Document not found: ${documentId}`);
1053
1075
  let document = loadedDocument;
1054
- const file = await requireReadyFile(db, workspaceId, document.fileId);
1076
+ const file = await requireReadyFile(db, {
1077
+ accountId: document.accountId,
1078
+ workspaceId,
1079
+ subjectId: cleanString(document.createdBy) ?? null,
1080
+ fileId: document.fileId
1081
+ });
1055
1082
  await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
1056
1083
  await scopedDb.update(schema.documents).set({
1057
1084
  status: "indexing",
@@ -1157,9 +1184,7 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
1157
1184
  if (!failed) throw error;
1158
1185
  return mapDocument(failed);
1159
1186
  }
1160
- const updated = await getDocument(db, workspaceId, documentId, {
1161
- viewerSubjectId: document.authoritySubjectId
1162
- });
1187
+ const updated = await getDocumentForIndexing(db, workspaceId, documentId);
1163
1188
  if (!updated) throw new Error(`Document disappeared after indexing: ${documentId}`);
1164
1189
  return updated;
1165
1190
  }
@@ -1325,7 +1350,11 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
1325
1350
  input.accountId,
1326
1351
  input.workspaceId,
1327
1352
  access,
1328
- async (scopedDb) => await scopedDb.select({ chunk: schema.documentChunks, document: schema.documents }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1353
+ async (scopedDb) => await scopedDb.select({
1354
+ chunk: schema.documentChunks,
1355
+ document: schema.documents,
1356
+ citation: googleDriveCitationProjection(input.workspaceId, access)
1357
+ }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1329
1358
  and(
1330
1359
  eq(schema.documents.accountId, input.accountId),
1331
1360
  eq(schema.documentChunks.accountId, input.accountId),
@@ -1345,7 +1374,7 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
1345
1374
  if (!row) return [];
1346
1375
  return [
1347
1376
  {
1348
- record: knowledgeChunkRecord(row.document, row.chunk),
1377
+ record: knowledgeChunkRecord(row.document, row.chunk, row.citation),
1349
1378
  retrieval: {
1350
1379
  score: rankedResult.score,
1351
1380
  matchType: rankedResult.matchType,
@@ -1368,7 +1397,10 @@ async function getEffectiveKnowledgeRecord(db, input) {
1368
1397
  access,
1369
1398
  async (scopedDb) => {
1370
1399
  if (target.kind === "document") {
1371
- const [document] = await scopedDb.select().from(schema.documents).where(
1400
+ const [row2] = await scopedDb.select({
1401
+ document: schema.documents,
1402
+ citation: googleDriveCitationProjection(input.workspaceId, access)
1403
+ }).from(schema.documents).where(
1372
1404
  and(
1373
1405
  eq(schema.documents.accountId, input.accountId),
1374
1406
  eq(schema.documents.id, target.id),
@@ -1376,9 +1408,13 @@ async function getEffectiveKnowledgeRecord(db, input) {
1376
1408
  ...documentAccessConditions(input.workspaceId, access)
1377
1409
  )
1378
1410
  ).limit(1);
1379
- return document ? knowledgeDocumentRecord(document) : null;
1411
+ return row2 ? knowledgeDocumentRecord(row2.document, row2.citation) : null;
1380
1412
  }
1381
- const [row] = await scopedDb.select({ chunk: schema.documentChunks, document: schema.documents }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1413
+ const [row] = await scopedDb.select({
1414
+ chunk: schema.documentChunks,
1415
+ document: schema.documents,
1416
+ citation: googleDriveCitationProjection(input.workspaceId, access)
1417
+ }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1382
1418
  and(
1383
1419
  eq(schema.documents.accountId, input.accountId),
1384
1420
  eq(schema.documentChunks.accountId, input.accountId),
@@ -1387,7 +1423,7 @@ async function getEffectiveKnowledgeRecord(db, input) {
1387
1423
  ...documentAccessConditions(input.workspaceId, access)
1388
1424
  )
1389
1425
  ).limit(1);
1390
- return row ? knowledgeChunkRecord(row.document, row.chunk) : null;
1426
+ return row ? knowledgeChunkRecord(row.document, row.chunk, row.citation) : null;
1391
1427
  }
1392
1428
  );
1393
1429
  }
@@ -1438,7 +1474,11 @@ async function browseEffectiveKnowledge(db, input) {
1438
1474
  )
1439
1475
  ).limit(1);
1440
1476
  if (!authorizedParent) return { records: [], nextCursor: null, hasMore: false };
1441
- const rows2 = await scopedDb.select({ chunk: schema.documentChunks, document: schema.documents }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1477
+ const rows2 = await scopedDb.select({
1478
+ chunk: schema.documentChunks,
1479
+ document: schema.documents,
1480
+ citation: googleDriveCitationProjection(input.workspaceId, access)
1481
+ }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1442
1482
  and(
1443
1483
  eq(schema.documentChunks.accountId, input.accountId),
1444
1484
  eq(schema.documentChunks.documentId, parent.id),
@@ -1451,7 +1491,7 @@ async function browseEffectiveKnowledge(db, input) {
1451
1491
  const page2 = rows2.slice(0, limit);
1452
1492
  const last2 = page2.at(-1)?.chunk.chunkIndex;
1453
1493
  return {
1454
- records: page2.map((row) => knowledgeChunkRecord(row.document, row.chunk)),
1494
+ records: page2.map((row) => knowledgeChunkRecord(row.document, row.chunk, row.citation)),
1455
1495
  nextCursor: hasMore2 && last2 !== void 0 ? encodeKnowledgeBrowseCursor(cursorScope, BigInt(last2 + 1)) : null,
1456
1496
  hasMore: hasMore2
1457
1497
  };
@@ -1466,12 +1506,15 @@ async function browseEffectiveKnowledge(db, input) {
1466
1506
  if (topic) conditions.push(sql`${schema.documents.topics} ? ${topic}`);
1467
1507
  if (sourceKinds.length > 0)
1468
1508
  conditions.push(inArray(schema.documents.sourceKind, sourceKinds));
1469
- const rows = await scopedDb.select().from(schema.documents).where(and(...conditions)).orderBy(asc(schema.documents.indexSequence)).limit(limit + 1);
1509
+ const rows = await scopedDb.select({
1510
+ document: schema.documents,
1511
+ citation: googleDriveCitationProjection(input.workspaceId, access)
1512
+ }).from(schema.documents).where(and(...conditions)).orderBy(asc(schema.documents.indexSequence)).limit(limit + 1);
1470
1513
  const hasMore = rows.length > limit;
1471
1514
  const page = rows.slice(0, limit);
1472
- const last = page.at(-1)?.indexSequence;
1515
+ const last = page.at(-1)?.document.indexSequence;
1473
1516
  return {
1474
- records: page.map(knowledgeDocumentRecord),
1517
+ records: page.map((row) => knowledgeDocumentRecord(row.document, row.citation)),
1475
1518
  nextCursor: hasMore && last !== void 0 && last !== null ? encodeKnowledgeBrowseCursor(cursorScope, last) : null,
1476
1519
  hasMore
1477
1520
  };
@@ -1519,7 +1562,7 @@ function parseKnowledgeRecordId(value) {
1519
1562
  if (!match) throw new Error("invalid knowledge record id");
1520
1563
  return { kind: match[1], id: match[2].toLowerCase() };
1521
1564
  }
1522
- function knowledgeDocumentRecord(document) {
1565
+ function knowledgeDocumentRecord(document, citation = null) {
1523
1566
  if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
1524
1567
  const projected = projectKnowledgeRecord({
1525
1568
  title: document.title,
@@ -1537,7 +1580,8 @@ function knowledgeDocumentRecord(document) {
1537
1580
  authority: { kind: normalizeDocumentAuthorityKind(document.authorityKind) },
1538
1581
  provenance: {
1539
1582
  source: projected.source,
1540
- indexedAt: document.indexedAt.toISOString()
1583
+ indexedAt: document.indexedAt.toISOString(),
1584
+ citation: parseKnowledgeProviderCitation(citation)
1541
1585
  },
1542
1586
  lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
1543
1587
  quality: knowledgeQuality(document),
@@ -1545,7 +1589,7 @@ function knowledgeDocumentRecord(document) {
1545
1589
  projection: projected.projection
1546
1590
  };
1547
1591
  }
1548
- function knowledgeChunkRecord(document, chunk) {
1592
+ function knowledgeChunkRecord(document, chunk, citation = null) {
1549
1593
  if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
1550
1594
  const projected = projectKnowledgeRecord({
1551
1595
  title: document.title,
@@ -1563,7 +1607,8 @@ function knowledgeChunkRecord(document, chunk) {
1563
1607
  authority: { kind: normalizeDocumentAuthorityKind(document.authorityKind) },
1564
1608
  provenance: {
1565
1609
  source: projected.source,
1566
- indexedAt: document.indexedAt.toISOString()
1610
+ indexedAt: document.indexedAt.toISOString(),
1611
+ citation: parseKnowledgeProviderCitation(citation)
1567
1612
  },
1568
1613
  lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
1569
1614
  quality: knowledgeQuality(document),
@@ -1629,6 +1674,7 @@ async function vectorSearchDocuments(db, input, limit, services) {
1629
1674
  authorityKind: schema.documents.authorityKind,
1630
1675
  authorityWorkspaceId: schema.documents.authorityWorkspaceId,
1631
1676
  authoritySubjectId: schema.documents.authoritySubjectId,
1677
+ citation: googleDriveCitationProjection(input.workspaceId, input.access),
1632
1678
  distance
1633
1679
  }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(and(...documentSearchConditions(input, services.embedder.model))).orderBy(distance).limit(limit)
1634
1680
  );
@@ -1667,6 +1713,7 @@ async function keywordSearchDocuments(db, input, limit) {
1667
1713
  authorityKind: schema.documents.authorityKind,
1668
1714
  authorityWorkspaceId: schema.documents.authorityWorkspaceId,
1669
1715
  authoritySubjectId: schema.documents.authoritySubjectId,
1716
+ citation: googleDriveCitationProjection(input.workspaceId, input.access),
1670
1717
  rank
1671
1718
  }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1672
1719
  and(
@@ -1708,7 +1755,8 @@ async function getDocumentChunk(db, accountId, workspaceId, chunkId, access) {
1708
1755
  aclTags: schema.documents.aclTags,
1709
1756
  authorityKind: schema.documents.authorityKind,
1710
1757
  authorityWorkspaceId: schema.documents.authorityWorkspaceId,
1711
- authoritySubjectId: schema.documents.authoritySubjectId
1758
+ authoritySubjectId: schema.documents.authoritySubjectId,
1759
+ citation: googleDriveCitationProjection(workspaceId, access)
1712
1760
  }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
1713
1761
  and(
1714
1762
  eq(schema.documents.accountId, accountId),
@@ -1781,10 +1829,17 @@ function documentAccessConditions(workspaceId, access) {
1781
1829
  eq(schema.documents.authoritySubjectId, viewer)
1782
1830
  ) : void 0;
1783
1831
  const authority = viewer ? or(organization, workspace, personal) ?? organization : or(organization, workspace) ?? organization;
1832
+ const viewerSql = viewer ? sql`${viewer}` : sql`NULL::text`;
1833
+ const providerAuthorization = sql`google_drive_file_authorized(
1834
+ ${schema.documents.accountId},
1835
+ ${workspaceId}::uuid,
1836
+ ${viewerSql},
1837
+ ${schema.documents.fileId}
1838
+ )`;
1784
1839
  if (access?.agentOnly) {
1785
- return [eq(schema.documents.agentAccess, true), authority];
1840
+ return [eq(schema.documents.agentAccess, true), authority, providerAuthorization];
1786
1841
  }
1787
- return [authority];
1842
+ return [authority, providerAuthorization];
1788
1843
  }
1789
1844
  function documentMatchesAccess(document, workspaceId, access) {
1790
1845
  if (access?.agentOnly) {
@@ -1863,9 +1918,24 @@ function mapSearchRowBase(row) {
1863
1918
  aclTags: cleanStringArray(row.aclTags),
1864
1919
  authorityKind: normalizeDocumentAuthorityKind(row.authorityKind),
1865
1920
  authorityWorkspaceId: row.authorityWorkspaceId,
1866
- authoritySubjectId: row.authoritySubjectId
1921
+ authoritySubjectId: row.authoritySubjectId,
1922
+ citation: parseKnowledgeProviderCitation(row.citation)
1867
1923
  };
1868
1924
  }
1925
+ function googleDriveCitationProjection(workspaceId, access) {
1926
+ const viewer = cleanString(access?.viewerSubjectId ?? null);
1927
+ const viewerSql = viewer ? sql`${viewer}` : sql`NULL::text`;
1928
+ return sql`google_drive_document_citation(
1929
+ ${schema.documents.accountId},
1930
+ ${workspaceId}::uuid,
1931
+ ${viewerSql},
1932
+ ${schema.documents.id},
1933
+ ${schema.documents.fileId}
1934
+ )`;
1935
+ }
1936
+ function parseKnowledgeProviderCitation(value) {
1937
+ return value === null || value === void 0 ? null : KnowledgeProviderCitation.parse(value);
1938
+ }
1869
1939
  function mergeDocumentSearchRows(rows, mode) {
1870
1940
  const byChunk = /* @__PURE__ */ new Map();
1871
1941
  for (const row of rows) {
@@ -1952,10 +2022,16 @@ function deterministicEmbedding(text, dimensions = DEFAULT_DOCUMENT_EMBEDDING_DI
1952
2022
  const norm = Math.hypot(...values) || 1;
1953
2023
  return values.map((value) => Number((value / norm).toFixed(6)));
1954
2024
  }
1955
- async function requireReadyFile(db, workspaceId, fileId) {
1956
- const file = await requireFile(db, workspaceId, fileId);
2025
+ async function requireReadyFile(db, input) {
2026
+ const [file] = await getFilesForSubject(db, {
2027
+ accountId: input.accountId,
2028
+ workspaceId: input.workspaceId,
2029
+ subjectId: input.subjectId,
2030
+ fileIds: [input.fileId]
2031
+ });
2032
+ if (!file) throw new Error(`File not found: ${input.fileId}`);
1957
2033
  if (file.status !== "ready") {
1958
- throw new Error(`File ${fileId} is ${file.status}`);
2034
+ throw new Error(`File ${input.fileId} is ${file.status}`);
1959
2035
  }
1960
2036
  return file;
1961
2037
  }
@@ -2175,6 +2251,7 @@ export {
2175
2251
  getDocument,
2176
2252
  getDocumentBase,
2177
2253
  getDocumentChunk,
2254
+ getDocumentForIndexing,
2178
2255
  getDocumentInventory,
2179
2256
  getEffectiveKnowledgeRecord,
2180
2257
  heuristicCuration,