@opengeni/documents 0.6.9-canary.0 → 0.8.2-canary.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/index.d.ts CHANGED
@@ -117,6 +117,14 @@ export type RunDocumentDefaultCollectionBackfillInput = RunDocumentDefaultCollec
117
117
  actorSubjectId: string;
118
118
  accountAdminAuthorization: DocumentAccountAdminAuthorization;
119
119
  };
120
+ export type DocumentMigrationAuditInput = {
121
+ accountId: string;
122
+ workspaceId: string;
123
+ actorSubjectId: string;
124
+ accountAdminAuthorization: DocumentAccountAdminAuthorization;
125
+ limit?: number | undefined;
126
+ cursor?: string | undefined;
127
+ };
120
128
  export type AgentDocumentAuthorityContext = {
121
129
  sessionId: string;
122
130
  attemptId: string;
@@ -158,6 +166,7 @@ export type DocumentSearchInput = {
158
166
  limit?: number | undefined;
159
167
  mode?: DocumentSearchMode | undefined;
160
168
  sourceKinds?: KnowledgeSourceKind[] | undefined;
169
+ authorityKinds?: DocumentAuthorityKind[] | undefined;
161
170
  aclTags?: string[] | undefined;
162
171
  access?: DocumentAccessFilter | undefined;
163
172
  };
@@ -321,6 +330,87 @@ export declare function runDocumentDefaultCollectionBackfill(db: Database, input
321
330
  adoptedCount: number;
322
331
  completedAt: string | null;
323
332
  }>;
333
+ export declare function listDocumentDefaultCollectionBackfillRuns(db: Database, input: DocumentMigrationAuditInput): Promise<{
334
+ runs: {
335
+ runId: string;
336
+ status: "completed" | "running";
337
+ lastWorkspaceId: string | null;
338
+ processedCount: number;
339
+ createdCount: number;
340
+ adoptedCount: number;
341
+ completedAt: string | null;
342
+ actorSubjectId: string;
343
+ startedAt: string;
344
+ updatedAt: string;
345
+ }[];
346
+ hasMore: boolean;
347
+ nextCursor: string | null;
348
+ }>;
349
+ export declare function getDocumentDefaultCollectionBackfillAudit(db: Database, input: DocumentMigrationAuditInput & {
350
+ runId: string;
351
+ operationCursor?: string | undefined;
352
+ receiptCursor?: string | undefined;
353
+ }): Promise<{
354
+ run: {
355
+ runId: string;
356
+ status: "completed" | "running";
357
+ lastWorkspaceId: string | null;
358
+ processedCount: number;
359
+ createdCount: number;
360
+ adoptedCount: number;
361
+ completedAt: string | null;
362
+ actorSubjectId: string;
363
+ startedAt: string;
364
+ updatedAt: string;
365
+ };
366
+ operations: {
367
+ operationId: string;
368
+ result: {
369
+ runId: string;
370
+ operationId: string;
371
+ status: "completed" | "running";
372
+ lastWorkspaceId: string | null;
373
+ processedCount: number;
374
+ createdCount: number;
375
+ adoptedCount: number;
376
+ completedAt: string | null;
377
+ };
378
+ createdAt: string;
379
+ }[];
380
+ receipts: {
381
+ workspaceId: string;
382
+ baseId: string;
383
+ outcome: "adopted" | "created";
384
+ createdAt: string;
385
+ }[];
386
+ operationsHasMore: boolean;
387
+ operationsNextCursor: string | null;
388
+ receiptsHasMore: boolean;
389
+ receiptsNextCursor: string | null;
390
+ }>;
391
+ export declare function listOrganizationDocumentAuthorityReclassifications(db: Database, input: DocumentMigrationAuditInput): Promise<{
392
+ receipts: {
393
+ operationId: string;
394
+ documentId: string;
395
+ previousAuthority: {
396
+ kind: "organization" | "personal" | "workspace";
397
+ workspaceId: string | null;
398
+ subjectId: string | null;
399
+ authorityId: string | null;
400
+ };
401
+ authority: {
402
+ kind: "organization" | "personal" | "workspace";
403
+ workspaceId: string | null;
404
+ subjectId: string | null;
405
+ authorityId: string | null;
406
+ };
407
+ createdAt: string;
408
+ actorSubjectId: string;
409
+ requestWorkspaceId: string;
410
+ }[];
411
+ hasMore: boolean;
412
+ nextCursor: string | null;
413
+ }>;
324
414
  export declare function reclassifyDocumentAuthority(db: Database, input: ReclassifyDocumentAuthorityInput): Promise<{
325
415
  operationId: string;
326
416
  documentId: string;
package/dist/index.js CHANGED
@@ -3,7 +3,14 @@ import {
3
3
  KnowledgeProviderCitation,
4
4
  DocumentAuthorityReclassification,
5
5
  ListDocumentAuthorityReclassificationsResponse,
6
- DocumentDefaultCollectionBackfill
6
+ DocumentDefaultCollectionBackfill,
7
+ DocumentDefaultCollectionBackfillAudit,
8
+ DocumentDefaultCollectionBackfillOperationAudit,
9
+ DocumentDefaultCollectionBackfillReceiptAudit,
10
+ DocumentDefaultCollectionBackfillRunAudit,
11
+ ListDocumentDefaultCollectionBackfillRunsResponse,
12
+ ListOrganizationDocumentAuthorityReclassificationsResponse,
13
+ OrganizationDocumentAuthorityReclassification
7
14
  } from "@opengeni/contracts";
8
15
  import {
9
16
  createPersonalDocumentAuthority,
@@ -740,6 +747,203 @@ async function runDocumentDefaultCollectionBackfill(db, input) {
740
747
  }
741
748
  );
742
749
  }
750
+ async function listDocumentDefaultCollectionBackfillRuns(db, input) {
751
+ const limit = validateDocumentMigrationAuditLimit(input.limit);
752
+ const cursor = input.cursor ? decodeDocumentMigrationAuditCursor(input.cursor, ["backfill-runs", input.accountId], true) : null;
753
+ return await withDocumentAccountRls(
754
+ db,
755
+ input.accountId,
756
+ input.workspaceId,
757
+ { viewerSubjectId: input.actorSubjectId },
758
+ async (scopedDb) => {
759
+ const command = {
760
+ accountId: input.accountId,
761
+ workspaceId: input.workspaceId,
762
+ actorSubjectId: input.actorSubjectId,
763
+ accountAdminAuthorization: input.accountAdminAuthorization,
764
+ limit: limit + 1,
765
+ beforeStartedAt: cursor?.timestamp ?? null,
766
+ beforeRunId: cursor?.id ?? null
767
+ };
768
+ const rows = await scopedDb.execute(sql`
769
+ SELECT list_document_default_collection_backfill_runs(
770
+ ${JSON.stringify(command)}::jsonb
771
+ ) AS result
772
+ `);
773
+ const parsed = rows.map((row) => DocumentDefaultCollectionBackfillRunAudit.parse(row.result));
774
+ const hasMore = parsed.length > limit;
775
+ const runs = parsed.slice(0, limit);
776
+ const tail = hasMore ? runs.at(-1) : null;
777
+ return ListDocumentDefaultCollectionBackfillRunsResponse.parse({
778
+ runs,
779
+ hasMore,
780
+ nextCursor: tail ? encodeDocumentMigrationAuditCursor(["backfill-runs", input.accountId], {
781
+ timestamp: tail.startedAt,
782
+ id: tail.runId
783
+ }) : null
784
+ });
785
+ }
786
+ );
787
+ }
788
+ async function getDocumentDefaultCollectionBackfillAudit(db, input) {
789
+ const limit = validateDocumentMigrationAuditLimit(input.limit);
790
+ const operationCursor = input.operationCursor ? decodeDocumentMigrationAuditCursor(
791
+ input.operationCursor,
792
+ ["backfill-operations", input.accountId, input.runId],
793
+ true
794
+ ) : null;
795
+ const receiptCursor = input.receiptCursor ? decodeDocumentMigrationAuditCursor(
796
+ input.receiptCursor,
797
+ ["backfill-receipts", input.accountId, input.runId],
798
+ false
799
+ ) : null;
800
+ return await withDocumentAccountRls(
801
+ db,
802
+ input.accountId,
803
+ input.workspaceId,
804
+ { viewerSubjectId: input.actorSubjectId },
805
+ async (scopedDb) => {
806
+ const command = {
807
+ accountId: input.accountId,
808
+ workspaceId: input.workspaceId,
809
+ actorSubjectId: input.actorSubjectId,
810
+ accountAdminAuthorization: input.accountAdminAuthorization,
811
+ runId: input.runId,
812
+ limit,
813
+ operationBeforeCreatedAt: operationCursor?.timestamp ?? null,
814
+ operationBeforeId: operationCursor?.id ?? null,
815
+ receiptAfterWorkspaceId: receiptCursor?.id ?? null
816
+ };
817
+ const rows = await scopedDb.execute(sql`
818
+ SELECT get_document_default_collection_backfill_audit(
819
+ ${JSON.stringify(command)}::jsonb
820
+ ) AS result
821
+ `);
822
+ const raw = rows[0]?.result;
823
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
824
+ throw new Error("document Default collection backfill audit returned no result");
825
+ }
826
+ const value = raw;
827
+ const run = DocumentDefaultCollectionBackfillRunAudit.parse(value.run);
828
+ const operations = Array.isArray(value.operations) ? value.operations.map(
829
+ (operation) => DocumentDefaultCollectionBackfillOperationAudit.parse(operation)
830
+ ) : [];
831
+ const receipts = Array.isArray(value.receipts) ? value.receipts.map(
832
+ (receipt) => DocumentDefaultCollectionBackfillReceiptAudit.parse(receipt)
833
+ ) : [];
834
+ const operationsHasMore = operations.length > limit;
835
+ const receiptsHasMore = receipts.length > limit;
836
+ const operationPage = operations.slice(0, limit);
837
+ const receiptPage = receipts.slice(0, limit);
838
+ const operationTail = operationsHasMore ? operationPage.at(-1) : null;
839
+ const receiptTail = receiptsHasMore ? receiptPage.at(-1) : null;
840
+ return DocumentDefaultCollectionBackfillAudit.parse({
841
+ run,
842
+ operations: operationPage,
843
+ receipts: receiptPage,
844
+ operationsHasMore,
845
+ operationsNextCursor: operationTail ? encodeDocumentMigrationAuditCursor(
846
+ ["backfill-operations", input.accountId, input.runId],
847
+ {
848
+ timestamp: operationTail.createdAt,
849
+ id: operationTail.operationId
850
+ }
851
+ ) : null,
852
+ receiptsHasMore,
853
+ receiptsNextCursor: receiptTail ? encodeDocumentMigrationAuditCursor(
854
+ ["backfill-receipts", input.accountId, input.runId],
855
+ { timestamp: null, id: receiptTail.workspaceId }
856
+ ) : null
857
+ });
858
+ }
859
+ );
860
+ }
861
+ async function listOrganizationDocumentAuthorityReclassifications(db, input) {
862
+ const limit = validateDocumentMigrationAuditLimit(input.limit);
863
+ const cursor = input.cursor ? decodeDocumentMigrationAuditCursor(
864
+ input.cursor,
865
+ ["organization-reclassifications", input.accountId],
866
+ true
867
+ ) : null;
868
+ return await withDocumentAccountRls(
869
+ db,
870
+ input.accountId,
871
+ input.workspaceId,
872
+ { viewerSubjectId: input.actorSubjectId },
873
+ async (scopedDb) => {
874
+ const command = {
875
+ accountId: input.accountId,
876
+ workspaceId: input.workspaceId,
877
+ actorSubjectId: input.actorSubjectId,
878
+ accountAdminAuthorization: input.accountAdminAuthorization,
879
+ limit: limit + 1,
880
+ beforeCreatedAt: cursor?.timestamp ?? null,
881
+ beforeOperationId: cursor?.id ?? null
882
+ };
883
+ const rows = await scopedDb.execute(sql`
884
+ SELECT list_organization_document_authority_reclassifications(
885
+ ${JSON.stringify(command)}::jsonb
886
+ ) AS result
887
+ `);
888
+ const parsed = rows.map(
889
+ (row) => OrganizationDocumentAuthorityReclassification.parse(row.result)
890
+ );
891
+ const hasMore = parsed.length > limit;
892
+ const receipts = parsed.slice(0, limit);
893
+ const tail = hasMore ? receipts.at(-1) : null;
894
+ return ListOrganizationDocumentAuthorityReclassificationsResponse.parse({
895
+ receipts,
896
+ hasMore,
897
+ nextCursor: tail ? encodeDocumentMigrationAuditCursor(
898
+ ["organization-reclassifications", input.accountId],
899
+ { timestamp: tail.createdAt, id: tail.operationId }
900
+ ) : null
901
+ });
902
+ }
903
+ );
904
+ }
905
+ function validateDocumentMigrationAuditLimit(value) {
906
+ const limit = value ?? 50;
907
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
908
+ throw new Error("document migration audit limit must be between 1 and 100");
909
+ }
910
+ return limit;
911
+ }
912
+ function documentMigrationAuditCursorScope(parts) {
913
+ return createHash("sha256").update(JSON.stringify(["document-migration-audit", 1, ...parts]), "utf8").digest("hex").slice(0, 32);
914
+ }
915
+ function encodeDocumentMigrationAuditCursor(parts, cursor) {
916
+ return Buffer.from(
917
+ JSON.stringify({
918
+ v: 1,
919
+ s: documentMigrationAuditCursorScope(parts),
920
+ t: cursor.timestamp,
921
+ i: cursor.id
922
+ }),
923
+ "utf8"
924
+ ).toString("base64url");
925
+ }
926
+ function decodeDocumentMigrationAuditCursor(value, parts, requireTimestamp) {
927
+ try {
928
+ if (!value || value.length > 1024) throw new Error("cursor length");
929
+ const bytes = Buffer.from(value, "base64url");
930
+ if (bytes.toString("base64url") !== value) throw new Error("cursor encoding");
931
+ const parsed = JSON.parse(bytes.toString("utf8"));
932
+ if (Object.keys(parsed).sort().join(",") !== "i,s,t,v" || parsed.v !== 1 || parsed.s !== documentMigrationAuditCursorScope(parts) || typeof parsed.i !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
933
+ parsed.i
934
+ ) || (requireTimestamp ? typeof parsed.t !== "string" || !Number.isFinite(new Date(parsed.t).getTime()) : parsed.t !== null)) {
935
+ throw new Error("cursor payload");
936
+ }
937
+ return {
938
+ timestamp: typeof parsed.t === "string" ? parsed.t : null,
939
+ id: parsed.i
940
+ };
941
+ } catch (error) {
942
+ throw new Error("invalid document migration audit cursor", {
943
+ cause: error
944
+ });
945
+ }
946
+ }
743
947
  async function reclassifyDocumentAuthority(db, input) {
744
948
  return await withDocumentAccountRls(
745
949
  db,
@@ -839,7 +1043,9 @@ function decodeDocumentAuthorityReclassificationCursor(value, scope) {
839
1043
  }
840
1044
  return { createdAt: parsed.t, operationId: parsed.i };
841
1045
  } catch (error) {
842
- throw new Error("invalid document authority receipt cursor", { cause: error });
1046
+ throw new Error("invalid document authority receipt cursor", {
1047
+ cause: error
1048
+ });
843
1049
  }
844
1050
  }
845
1051
  async function addDocumentToBase(db, input) {
@@ -1380,7 +1586,11 @@ async function curateDroppedDocument(db, services, document, parsed, file) {
1380
1586
  return updated2 ?? document;
1381
1587
  }
1382
1588
  const bases = await listDocumentBases(db, document.workspaceId);
1383
- const candidates = bases.filter((base) => base.id !== document.baseId).map((base) => ({ id: base.id, name: base.name, description: base.description }));
1589
+ const candidates = bases.filter((base) => base.id !== document.baseId).map((base) => ({
1590
+ id: base.id,
1591
+ name: base.name,
1592
+ description: base.description
1593
+ }));
1384
1594
  const input = {
1385
1595
  text: parsed.text.slice(0, DOCUMENT_CURATION_MAX_INPUT_CHARS),
1386
1596
  filename: file.filename,
@@ -1519,6 +1729,7 @@ async function searchEffectiveDocuments(db, input, services = createDocumentServ
1519
1729
  limit: input.limit,
1520
1730
  mode: input.mode,
1521
1731
  sourceKinds: input.sourceKinds,
1732
+ authorityKinds: input.authorityKinds,
1522
1733
  aclTags: input.aclTags,
1523
1734
  // Construct the lower-level access filter here instead of spreading the
1524
1735
  // caller input, so an untyped/legacy access override is always ignored.
@@ -1548,6 +1759,7 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
1548
1759
  ...input.baseIds ? { baseIds: input.baseIds } : {},
1549
1760
  ...input.mode ? { mode: input.mode } : {},
1550
1761
  ...input.sourceKinds ? { sourceKinds: input.sourceKinds } : {},
1762
+ ...input.authorityKinds ? { authorityKinds: input.authorityKinds } : {},
1551
1763
  ...input.aclTags ? { aclTags: input.aclTags } : {},
1552
1764
  access
1553
1765
  },
@@ -1586,6 +1798,7 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
1586
1798
  ranked.map((result) => result.chunkId)
1587
1799
  ),
1588
1800
  eq(schema.documents.status, "ready"),
1801
+ ...input.authorityKinds?.length ? [inArray(schema.documents.authorityKind, input.authorityKinds)] : [],
1589
1802
  ...documentAccessConditions(input.workspaceId, access)
1590
1803
  )
1591
1804
  )
@@ -1986,7 +2199,10 @@ async function browseEffectiveKnowledge(db, input) {
1986
2199
  access,
1987
2200
  async (scopedDb) => {
1988
2201
  if (parent) {
1989
- const [authorizedParent] = await scopedDb.select({ id: schema.documents.id, indexSequence: schema.documents.indexSequence }).from(schema.documents).where(
2202
+ const [authorizedParent] = await scopedDb.select({
2203
+ id: schema.documents.id,
2204
+ indexSequence: schema.documents.indexSequence
2205
+ }).from(schema.documents).where(
1990
2206
  and(
1991
2207
  eq(schema.documents.accountId, input.accountId),
1992
2208
  eq(schema.documents.id, parent.id),
@@ -1995,7 +2211,10 @@ async function browseEffectiveKnowledge(db, input) {
1995
2211
  )
1996
2212
  ).limit(1);
1997
2213
  if (!authorizedParent) {
1998
- return selectKnowledgeBrowseRecords({ entries: [], hasMoreAfterEntries: false });
2214
+ return selectKnowledgeBrowseRecords({
2215
+ entries: [],
2216
+ hasMoreAfterEntries: false
2217
+ });
1999
2218
  }
2000
2219
  if (authorizedParent.indexSequence === null) {
2001
2220
  throw new Error("ready knowledge document is missing its index revision");
@@ -2083,7 +2302,11 @@ function encodeKnowledgeBrowseCursor(scope, position) {
2083
2302
  if (position < 0n) throw new Error("knowledge browse cursor position is invalid");
2084
2303
  const version = knowledgeBrowseCursorVersion(scope);
2085
2304
  return Buffer.from(
2086
- JSON.stringify({ v: version, s: knowledgeBrowseCursorScope(scope), q: position.toString() }),
2305
+ JSON.stringify({
2306
+ v: version,
2307
+ s: knowledgeBrowseCursorScope(scope),
2308
+ q: position.toString()
2309
+ }),
2087
2310
  "utf8"
2088
2311
  ).toString("base64url");
2089
2312
  }
@@ -2130,7 +2353,10 @@ function parseKnowledgeRecordId(value) {
2130
2353
  value
2131
2354
  );
2132
2355
  if (!match) throw new Error("invalid knowledge record id");
2133
- return { kind: match[1], id: match[2].toLowerCase() };
2356
+ return {
2357
+ kind: match[1],
2358
+ id: match[2].toLowerCase()
2359
+ };
2134
2360
  }
2135
2361
  function knowledgeDocumentRecord(document, citation = null, firstChunkId = null) {
2136
2362
  if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
@@ -2547,6 +2773,9 @@ function documentSearchConditions(input, embeddingModel) {
2547
2773
  if (input.sourceKinds?.length) {
2548
2774
  conditions.push(inArray(schema.documents.sourceKind, input.sourceKinds));
2549
2775
  }
2776
+ if (input.authorityKinds?.length) {
2777
+ conditions.push(inArray(schema.documents.authorityKind, input.authorityKinds));
2778
+ }
2550
2779
  const aclTags = cleanStringArray(input.aclTags);
2551
2780
  if (aclTags.length > 0) {
2552
2781
  conditions.push(sql`${schema.documents.aclTags} @> ${JSON.stringify(aclTags)}::jsonb`);
@@ -2909,6 +3138,7 @@ export {
2909
3138
  getDocument,
2910
3139
  getDocumentBase,
2911
3140
  getDocumentChunk,
3141
+ getDocumentDefaultCollectionBackfillAudit,
2912
3142
  getDocumentForIndexing,
2913
3143
  getDocumentInventory,
2914
3144
  getDocumentOriginalFile,
@@ -2919,8 +3149,10 @@ export {
2919
3149
  listDocumentAuthorityReclassifications,
2920
3150
  listDocumentBases,
2921
3151
  listDocumentBasesEnsuringDefault,
3152
+ listDocumentDefaultCollectionBackfillRuns,
2922
3153
  listDocuments,
2923
3154
  listEffectiveIndexedDocuments,
3155
+ listOrganizationDocumentAuthorityReclassifications,
2924
3156
  moveDocumentToBase,
2925
3157
  parseCurationOutcome,
2926
3158
  parseDocumentBytes,