@opengeni/documents 0.5.41 → 0.7.0-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 +258 -5
- package/dist/index.js +991 -125
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +1367 -129
package/dist/index.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
KnowledgeProviderCitation
|
|
3
|
+
KnowledgeProviderCitation,
|
|
4
|
+
DocumentAuthorityReclassification,
|
|
5
|
+
ListDocumentAuthorityReclassificationsResponse,
|
|
6
|
+
DocumentDefaultCollectionBackfill,
|
|
7
|
+
DocumentDefaultCollectionBackfillAudit,
|
|
8
|
+
DocumentDefaultCollectionBackfillOperationAudit,
|
|
9
|
+
DocumentDefaultCollectionBackfillReceiptAudit,
|
|
10
|
+
DocumentDefaultCollectionBackfillRunAudit,
|
|
11
|
+
ListDocumentDefaultCollectionBackfillRunsResponse,
|
|
12
|
+
ListOrganizationDocumentAuthorityReclassificationsResponse,
|
|
13
|
+
OrganizationDocumentAuthorityReclassification
|
|
4
14
|
} from "@opengeni/contracts";
|
|
5
15
|
import {
|
|
16
|
+
createPersonalDocumentAuthority,
|
|
6
17
|
getFilesForSubject,
|
|
18
|
+
resolveDocumentOriginalFileForSubject,
|
|
7
19
|
rlsContextForWorkspace,
|
|
8
20
|
setSubjectRlsContext,
|
|
9
21
|
withRlsContext,
|
|
@@ -11,9 +23,20 @@ import {
|
|
|
11
23
|
withWorkspaceSubjectRls
|
|
12
24
|
} from "@opengeni/db";
|
|
13
25
|
import * as schema from "@opengeni/db/schema";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
26
|
+
import { retryWhileMissing } from "@opengeni/storage";
|
|
27
|
+
import { createHash, randomUUID } from "crypto";
|
|
28
|
+
import { and, asc, desc, eq, gt, inArray, isNotNull, isNull, or, sql } from "drizzle-orm";
|
|
29
|
+
import {
|
|
30
|
+
KNOWLEDGE_BROWSE_CURSOR_MAX_CHARS,
|
|
31
|
+
KNOWLEDGE_BROWSE_MAX_LIMIT,
|
|
32
|
+
KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES,
|
|
33
|
+
KNOWLEDGE_SEARCH_MAX_RESPONSE_BYTES,
|
|
34
|
+
KNOWLEDGE_SEARCH_MAX_FLOOR_OMISSIONS,
|
|
35
|
+
KNOWLEDGE_SEARCH_MAX_RESULTS,
|
|
36
|
+
KNOWLEDGE_SEARCH_MIN_KEYWORD_SCORE,
|
|
37
|
+
KNOWLEDGE_SEARCH_MIN_VECTOR_SCORE,
|
|
38
|
+
KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN
|
|
39
|
+
} from "@opengeni/contracts";
|
|
17
40
|
|
|
18
41
|
// src/knowledge-projection.ts
|
|
19
42
|
import {
|
|
@@ -697,6 +720,324 @@ async function listDocumentBasesEnsuringDefault(db, input) {
|
|
|
697
720
|
await ensureDefaultBase(db, input);
|
|
698
721
|
return await listDocumentBases(db, input.workspaceId);
|
|
699
722
|
}
|
|
723
|
+
async function runDocumentDefaultCollectionBackfill(db, input) {
|
|
724
|
+
return await withDocumentAccountRls(
|
|
725
|
+
db,
|
|
726
|
+
input.accountId,
|
|
727
|
+
input.workspaceId,
|
|
728
|
+
{ viewerSubjectId: input.actorSubjectId },
|
|
729
|
+
async (scopedDb) => {
|
|
730
|
+
const command = {
|
|
731
|
+
accountId: input.accountId,
|
|
732
|
+
workspaceId: input.workspaceId,
|
|
733
|
+
actorSubjectId: input.actorSubjectId,
|
|
734
|
+
runId: input.runId,
|
|
735
|
+
operationId: input.operationId,
|
|
736
|
+
batchSize: input.batchSize,
|
|
737
|
+
accountAdminAuthorization: input.accountAdminAuthorization
|
|
738
|
+
};
|
|
739
|
+
const rows = await scopedDb.execute(sql`
|
|
740
|
+
SELECT run_document_default_collection_backfill(
|
|
741
|
+
${JSON.stringify(command)}::jsonb
|
|
742
|
+
) AS result
|
|
743
|
+
`);
|
|
744
|
+
const row = rows[0];
|
|
745
|
+
if (!row) throw new Error("Document Default collection backfill returned no result");
|
|
746
|
+
return DocumentDefaultCollectionBackfill.parse(row.result);
|
|
747
|
+
}
|
|
748
|
+
);
|
|
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
|
+
{ timestamp: operationTail.createdAt, id: operationTail.operationId }
|
|
848
|
+
) : null,
|
|
849
|
+
receiptsHasMore,
|
|
850
|
+
receiptsNextCursor: receiptTail ? encodeDocumentMigrationAuditCursor(
|
|
851
|
+
["backfill-receipts", input.accountId, input.runId],
|
|
852
|
+
{ timestamp: null, id: receiptTail.workspaceId }
|
|
853
|
+
) : null
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
async function listOrganizationDocumentAuthorityReclassifications(db, input) {
|
|
859
|
+
const limit = validateDocumentMigrationAuditLimit(input.limit);
|
|
860
|
+
const cursor = input.cursor ? decodeDocumentMigrationAuditCursor(
|
|
861
|
+
input.cursor,
|
|
862
|
+
["organization-reclassifications", input.accountId],
|
|
863
|
+
true
|
|
864
|
+
) : null;
|
|
865
|
+
return await withDocumentAccountRls(
|
|
866
|
+
db,
|
|
867
|
+
input.accountId,
|
|
868
|
+
input.workspaceId,
|
|
869
|
+
{ viewerSubjectId: input.actorSubjectId },
|
|
870
|
+
async (scopedDb) => {
|
|
871
|
+
const command = {
|
|
872
|
+
accountId: input.accountId,
|
|
873
|
+
workspaceId: input.workspaceId,
|
|
874
|
+
actorSubjectId: input.actorSubjectId,
|
|
875
|
+
accountAdminAuthorization: input.accountAdminAuthorization,
|
|
876
|
+
limit: limit + 1,
|
|
877
|
+
beforeCreatedAt: cursor?.timestamp ?? null,
|
|
878
|
+
beforeOperationId: cursor?.id ?? null
|
|
879
|
+
};
|
|
880
|
+
const rows = await scopedDb.execute(sql`
|
|
881
|
+
SELECT list_organization_document_authority_reclassifications(
|
|
882
|
+
${JSON.stringify(command)}::jsonb
|
|
883
|
+
) AS result
|
|
884
|
+
`);
|
|
885
|
+
const parsed = rows.map(
|
|
886
|
+
(row) => OrganizationDocumentAuthorityReclassification.parse(row.result)
|
|
887
|
+
);
|
|
888
|
+
const hasMore = parsed.length > limit;
|
|
889
|
+
const receipts = parsed.slice(0, limit);
|
|
890
|
+
const tail = hasMore ? receipts.at(-1) : null;
|
|
891
|
+
return ListOrganizationDocumentAuthorityReclassificationsResponse.parse({
|
|
892
|
+
receipts,
|
|
893
|
+
hasMore,
|
|
894
|
+
nextCursor: tail ? encodeDocumentMigrationAuditCursor(
|
|
895
|
+
["organization-reclassifications", input.accountId],
|
|
896
|
+
{ timestamp: tail.createdAt, id: tail.operationId }
|
|
897
|
+
) : null
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
function validateDocumentMigrationAuditLimit(value) {
|
|
903
|
+
const limit = value ?? 50;
|
|
904
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
905
|
+
throw new Error("document migration audit limit must be between 1 and 100");
|
|
906
|
+
}
|
|
907
|
+
return limit;
|
|
908
|
+
}
|
|
909
|
+
function documentMigrationAuditCursorScope(parts) {
|
|
910
|
+
return createHash("sha256").update(JSON.stringify(["document-migration-audit", 1, ...parts]), "utf8").digest("hex").slice(0, 32);
|
|
911
|
+
}
|
|
912
|
+
function encodeDocumentMigrationAuditCursor(parts, cursor) {
|
|
913
|
+
return Buffer.from(
|
|
914
|
+
JSON.stringify({
|
|
915
|
+
v: 1,
|
|
916
|
+
s: documentMigrationAuditCursorScope(parts),
|
|
917
|
+
t: cursor.timestamp,
|
|
918
|
+
i: cursor.id
|
|
919
|
+
}),
|
|
920
|
+
"utf8"
|
|
921
|
+
).toString("base64url");
|
|
922
|
+
}
|
|
923
|
+
function decodeDocumentMigrationAuditCursor(value, parts, requireTimestamp) {
|
|
924
|
+
try {
|
|
925
|
+
if (!value || value.length > 1024) throw new Error("cursor length");
|
|
926
|
+
const bytes = Buffer.from(value, "base64url");
|
|
927
|
+
if (bytes.toString("base64url") !== value) throw new Error("cursor encoding");
|
|
928
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
929
|
+
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(
|
|
930
|
+
parsed.i
|
|
931
|
+
) || (requireTimestamp ? typeof parsed.t !== "string" || !Number.isFinite(new Date(parsed.t).getTime()) : parsed.t !== null)) {
|
|
932
|
+
throw new Error("cursor payload");
|
|
933
|
+
}
|
|
934
|
+
return { timestamp: typeof parsed.t === "string" ? parsed.t : null, id: parsed.i };
|
|
935
|
+
} catch (error) {
|
|
936
|
+
throw new Error("invalid document migration audit cursor", { cause: error });
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async function reclassifyDocumentAuthority(db, input) {
|
|
940
|
+
return await withDocumentAccountRls(
|
|
941
|
+
db,
|
|
942
|
+
input.accountId,
|
|
943
|
+
input.workspaceId,
|
|
944
|
+
{ viewerSubjectId: input.actorSubjectId },
|
|
945
|
+
async (scopedDb) => {
|
|
946
|
+
const command = {
|
|
947
|
+
accountId: input.accountId,
|
|
948
|
+
workspaceId: input.workspaceId,
|
|
949
|
+
documentId: input.documentId,
|
|
950
|
+
operationId: input.operationId,
|
|
951
|
+
actorSubjectId: input.actorSubjectId,
|
|
952
|
+
expectedAuthority: input.expectedAuthority,
|
|
953
|
+
targetAuthorityKind: input.targetAuthorityKind,
|
|
954
|
+
accountAdminAuthorization: input.accountAdminAuthorization
|
|
955
|
+
};
|
|
956
|
+
const rows = await scopedDb.execute(sql`
|
|
957
|
+
SELECT reclassify_document_authority(${JSON.stringify(command)}::jsonb) AS result
|
|
958
|
+
`);
|
|
959
|
+
const row = rows[0];
|
|
960
|
+
if (!row) throw new Error("Document authority reclassification returned no result");
|
|
961
|
+
return DocumentAuthorityReclassification.parse(row.result);
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
async function listDocumentAuthorityReclassifications(db, input) {
|
|
966
|
+
const limit = input.limit ?? 50;
|
|
967
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
968
|
+
throw new Error("document authority receipt limit must be between 1 and 100");
|
|
969
|
+
}
|
|
970
|
+
const cursor = input.cursor ? decodeDocumentAuthorityReclassificationCursor(input.cursor, input) : null;
|
|
971
|
+
return await withDocumentAccountRls(
|
|
972
|
+
db,
|
|
973
|
+
input.accountId,
|
|
974
|
+
input.workspaceId,
|
|
975
|
+
{ viewerSubjectId: input.actorSubjectId },
|
|
976
|
+
async (scopedDb) => {
|
|
977
|
+
const rows = await scopedDb.execute(sql`
|
|
978
|
+
SELECT list_document_authority_reclassifications(
|
|
979
|
+
${input.accountId}::uuid,
|
|
980
|
+
${input.workspaceId}::uuid,
|
|
981
|
+
${input.actorSubjectId},
|
|
982
|
+
${input.documentId}::uuid,
|
|
983
|
+
${limit + 1}::integer,
|
|
984
|
+
${cursor?.createdAt ?? null}::timestamptz,
|
|
985
|
+
${cursor?.operationId ?? null}::uuid
|
|
986
|
+
) AS result
|
|
987
|
+
`);
|
|
988
|
+
const parsed = rows.map((row) => DocumentAuthorityReclassification.parse(row.result));
|
|
989
|
+
const hasMore = parsed.length > limit;
|
|
990
|
+
const receipts = parsed.slice(0, limit);
|
|
991
|
+
const tail = hasMore ? receipts.at(-1) : null;
|
|
992
|
+
return ListDocumentAuthorityReclassificationsResponse.parse({
|
|
993
|
+
receipts,
|
|
994
|
+
hasMore,
|
|
995
|
+
nextCursor: tail ? encodeDocumentAuthorityReclassificationCursor(input, {
|
|
996
|
+
createdAt: tail.createdAt,
|
|
997
|
+
operationId: tail.operationId
|
|
998
|
+
}) : null
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
function documentAuthorityReclassificationCursorScope(input) {
|
|
1004
|
+
return createHash("sha256").update(
|
|
1005
|
+
JSON.stringify([
|
|
1006
|
+
"document_authority_reclassification_cursor",
|
|
1007
|
+
1,
|
|
1008
|
+
input.accountId,
|
|
1009
|
+
input.workspaceId,
|
|
1010
|
+
input.documentId,
|
|
1011
|
+
input.actorSubjectId
|
|
1012
|
+
]),
|
|
1013
|
+
"utf8"
|
|
1014
|
+
).digest("hex").slice(0, 32);
|
|
1015
|
+
}
|
|
1016
|
+
function encodeDocumentAuthorityReclassificationCursor(scope, cursor) {
|
|
1017
|
+
return Buffer.from(
|
|
1018
|
+
JSON.stringify({
|
|
1019
|
+
v: 1,
|
|
1020
|
+
s: documentAuthorityReclassificationCursorScope(scope),
|
|
1021
|
+
t: cursor.createdAt,
|
|
1022
|
+
i: cursor.operationId
|
|
1023
|
+
}),
|
|
1024
|
+
"utf8"
|
|
1025
|
+
).toString("base64url");
|
|
1026
|
+
}
|
|
1027
|
+
function decodeDocumentAuthorityReclassificationCursor(value, scope) {
|
|
1028
|
+
try {
|
|
1029
|
+
if (!value || value.length > 1024) throw new Error("cursor length");
|
|
1030
|
+
const bytes = Buffer.from(value, "base64url");
|
|
1031
|
+
if (bytes.toString("base64url") !== value) throw new Error("cursor encoding");
|
|
1032
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
1033
|
+
if (Object.keys(parsed).sort().join(",") !== "i,s,t,v" || parsed.v !== 1 || parsed.s !== documentAuthorityReclassificationCursorScope(scope) || typeof parsed.t !== "string" || !Number.isFinite(new Date(parsed.t).getTime()) || 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(parsed.i)) {
|
|
1034
|
+
throw new Error("cursor payload");
|
|
1035
|
+
}
|
|
1036
|
+
return { createdAt: parsed.t, operationId: parsed.i };
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
throw new Error("invalid document authority receipt cursor", { cause: error });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
700
1041
|
async function addDocumentToBase(db, input) {
|
|
701
1042
|
return await withRlsContext(
|
|
702
1043
|
db,
|
|
@@ -776,33 +1117,47 @@ async function addDocumentToBase(db, input) {
|
|
|
776
1117
|
).returning();
|
|
777
1118
|
return mapDocument(updated ?? existing);
|
|
778
1119
|
}
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1120
|
+
const documentId = randomUUID();
|
|
1121
|
+
const row = await scopedDb.transaction(async (tx) => {
|
|
1122
|
+
const userAuthority = authority.kind === "personal" ? await createPersonalDocumentAuthority(tx, {
|
|
1123
|
+
accountId: input.accountId,
|
|
1124
|
+
workspaceId: input.workspaceId,
|
|
1125
|
+
subjectId: authority.subjectId,
|
|
1126
|
+
documentId
|
|
1127
|
+
}) : null;
|
|
1128
|
+
const [inserted] = await tx.insert(schema.documents).values({
|
|
1129
|
+
id: documentId,
|
|
1130
|
+
accountId: input.accountId,
|
|
1131
|
+
workspaceId: input.workspaceId,
|
|
1132
|
+
baseId: input.baseId,
|
|
1133
|
+
fileId: input.fileId,
|
|
1134
|
+
status: "queued",
|
|
1135
|
+
title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? file.filename,
|
|
1136
|
+
parser: DEFAULT_DOCUMENT_PARSER,
|
|
1137
|
+
sourceKind: input.sourceKind ?? "manual_upload",
|
|
1138
|
+
sourceUri: cleanString(input.sourceUri) ?? null,
|
|
1139
|
+
sourceExternalId: cleanString(input.sourceExternalId) ?? null,
|
|
1140
|
+
sourceTitle: cleanString(input.sourceTitle) ?? null,
|
|
1141
|
+
sourceAuthor: cleanString(input.sourceAuthor) ?? null,
|
|
1142
|
+
sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),
|
|
1143
|
+
sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),
|
|
1144
|
+
sourceVersion: cleanString(input.sourceVersion) ?? null,
|
|
1145
|
+
knowledgeSourceIdentity,
|
|
1146
|
+
aclTags: cleanStringArray(input.aclTags),
|
|
1147
|
+
authorityKind: authority.kind,
|
|
1148
|
+
authorityWorkspaceId: userAuthority ? null : authority.workspaceId,
|
|
1149
|
+
authoritySubjectId: authority.subjectId,
|
|
1150
|
+
authorityId: userAuthority?.authorityId ?? null,
|
|
1151
|
+
ownerOrganizationMembershipId: userAuthority?.ownerOrganizationMembershipId ?? null,
|
|
1152
|
+
originWorkspaceId: input.workspaceId,
|
|
1153
|
+
visibility: authority.kind === "personal" ? "private" : "workspace",
|
|
1154
|
+
agentAccess: input.agentAccess ?? true,
|
|
1155
|
+
createdBy: fileAuthoritySubjectId,
|
|
1156
|
+
curationStatus: input.curationStatus ?? "none",
|
|
1157
|
+
updatedAt: now
|
|
1158
|
+
}).returning();
|
|
1159
|
+
return inserted;
|
|
1160
|
+
});
|
|
806
1161
|
if (!row) throw new Error("Failed to create document");
|
|
807
1162
|
return mapDocument(row);
|
|
808
1163
|
}
|
|
@@ -817,7 +1172,7 @@ async function moveDocumentToBase(db, input) {
|
|
|
817
1172
|
if (viewerSubjectId) await setSubjectRlsContext(scopedDb, viewerSubjectId);
|
|
818
1173
|
const [row] = await scopedDb.select().from(schema.documents).where(
|
|
819
1174
|
and(
|
|
820
|
-
eq(schema.documents.
|
|
1175
|
+
eq(schema.documents.accountId, input.accountId),
|
|
821
1176
|
eq(schema.documents.id, input.documentId),
|
|
822
1177
|
...documentAccessConditions(input.workspaceId, input.access)
|
|
823
1178
|
)
|
|
@@ -830,11 +1185,11 @@ async function moveDocumentToBase(db, input) {
|
|
|
830
1185
|
throw new Error("document has no suggested base; pass targetBaseId");
|
|
831
1186
|
}
|
|
832
1187
|
if (targetBaseId === row.baseId) return mapDocument(row);
|
|
833
|
-
const base = await getDocumentBase(scopedDb,
|
|
1188
|
+
const base = await getDocumentBase(scopedDb, row.workspaceId, targetBaseId);
|
|
834
1189
|
if (!base) throw new Error(`Document base not found: ${targetBaseId}`);
|
|
835
1190
|
const [conflict] = await scopedDb.select({ id: schema.documents.id }).from(schema.documents).where(
|
|
836
1191
|
and(
|
|
837
|
-
eq(schema.documents.workspaceId,
|
|
1192
|
+
eq(schema.documents.workspaceId, row.workspaceId),
|
|
838
1193
|
eq(schema.documents.baseId, targetBaseId),
|
|
839
1194
|
...row.knowledgeSourceIdentity ? [eq(schema.documents.knowledgeSourceIdentity, row.knowledgeSourceIdentity)] : [eq(schema.documents.fileId, row.fileId)]
|
|
840
1195
|
)
|
|
@@ -850,7 +1205,7 @@ async function moveDocumentToBase(db, input) {
|
|
|
850
1205
|
updatedAt: now
|
|
851
1206
|
}).where(
|
|
852
1207
|
and(
|
|
853
|
-
eq(schema.documents.workspaceId,
|
|
1208
|
+
eq(schema.documents.workspaceId, row.workspaceId),
|
|
854
1209
|
eq(schema.documents.id, input.documentId),
|
|
855
1210
|
...documentAccessConditions(input.workspaceId, input.access)
|
|
856
1211
|
)
|
|
@@ -858,7 +1213,7 @@ async function moveDocumentToBase(db, input) {
|
|
|
858
1213
|
if (updated) {
|
|
859
1214
|
await tx.update(schema.documentChunks).set({ baseId: targetBaseId }).where(
|
|
860
1215
|
and(
|
|
861
|
-
eq(schema.documentChunks.workspaceId,
|
|
1216
|
+
eq(schema.documentChunks.workspaceId, row.workspaceId),
|
|
862
1217
|
eq(schema.documentChunks.documentId, input.documentId)
|
|
863
1218
|
)
|
|
864
1219
|
);
|
|
@@ -879,7 +1234,7 @@ async function deleteDocumentFromBase(db, input) {
|
|
|
879
1234
|
if (viewerSubjectId) await setSubjectRlsContext(scopedDb, viewerSubjectId);
|
|
880
1235
|
const [document] = await scopedDb.select().from(schema.documents).where(
|
|
881
1236
|
and(
|
|
882
|
-
eq(schema.documents.
|
|
1237
|
+
eq(schema.documents.accountId, input.accountId),
|
|
883
1238
|
eq(schema.documents.id, input.documentId),
|
|
884
1239
|
...documentAccessConditions(input.workspaceId, input.access)
|
|
885
1240
|
)
|
|
@@ -896,7 +1251,7 @@ async function deleteDocumentFromBase(db, input) {
|
|
|
896
1251
|
}
|
|
897
1252
|
await scopedDb.delete(schema.documents).where(
|
|
898
1253
|
and(
|
|
899
|
-
eq(schema.documents.
|
|
1254
|
+
eq(schema.documents.accountId, input.accountId),
|
|
900
1255
|
eq(schema.documents.id, input.documentId),
|
|
901
1256
|
...documentAccessConditions(input.workspaceId, input.access)
|
|
902
1257
|
)
|
|
@@ -916,6 +1271,12 @@ async function listDocuments(db, workspaceId, baseId, access) {
|
|
|
916
1271
|
return rows.map(mapDocument);
|
|
917
1272
|
});
|
|
918
1273
|
}
|
|
1274
|
+
async function listAccessibleDocuments(db, workspaceId, access) {
|
|
1275
|
+
return await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1276
|
+
const rows = await scopedDb.select().from(schema.documents).where(and(...documentAccessConditions(workspaceId, access))).orderBy(desc(schema.documents.updatedAt), asc(schema.documents.createdAt));
|
|
1277
|
+
return rows.map(mapDocument);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
919
1280
|
async function listEffectiveIndexedDocuments(db, input) {
|
|
920
1281
|
const initiatingSubjectId = canonicalEffectiveDocumentSubject(input.initiatingSubjectId);
|
|
921
1282
|
const limit = input.limit ?? 50;
|
|
@@ -927,10 +1288,13 @@ async function listEffectiveIndexedDocuments(db, input) {
|
|
|
927
1288
|
workspaceId: input.workspaceId,
|
|
928
1289
|
initiatingSubjectId
|
|
929
1290
|
}) : 0n;
|
|
930
|
-
const access = {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1291
|
+
const access = await resolveEffectiveDocumentAccess(db, {
|
|
1292
|
+
accountId: input.accountId,
|
|
1293
|
+
workspaceId: input.workspaceId,
|
|
1294
|
+
initiatingSubjectId,
|
|
1295
|
+
surface: "agent",
|
|
1296
|
+
agentAuthority: input.agentAuthority
|
|
1297
|
+
});
|
|
934
1298
|
const rows = await withDocumentAccountRls(
|
|
935
1299
|
db,
|
|
936
1300
|
input.accountId,
|
|
@@ -1014,15 +1378,21 @@ function canonicalEffectiveDocumentSubject(value) {
|
|
|
1014
1378
|
async function getDocument(db, workspaceId, documentId, access) {
|
|
1015
1379
|
return await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1016
1380
|
const [row] = await scopedDb.select().from(schema.documents).where(
|
|
1017
|
-
and(
|
|
1018
|
-
eq(schema.documents.workspaceId, workspaceId),
|
|
1019
|
-
eq(schema.documents.id, documentId),
|
|
1020
|
-
...documentAccessConditions(workspaceId, access)
|
|
1021
|
-
)
|
|
1381
|
+
and(eq(schema.documents.id, documentId), ...documentAccessConditions(workspaceId, access))
|
|
1022
1382
|
).limit(1);
|
|
1023
1383
|
return row ? mapDocument(row) : null;
|
|
1024
1384
|
});
|
|
1025
1385
|
}
|
|
1386
|
+
async function getDocumentOriginalFile(db, input) {
|
|
1387
|
+
const subjectId = cleanString(input.access.viewerSubjectId ?? null);
|
|
1388
|
+
if (!subjectId || input.access.agentOnly) return null;
|
|
1389
|
+
return await resolveDocumentOriginalFileForSubject(db, {
|
|
1390
|
+
accountId: input.accountId,
|
|
1391
|
+
workspaceId: input.workspaceId,
|
|
1392
|
+
subjectId,
|
|
1393
|
+
documentId: input.documentId
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1026
1396
|
async function getDocumentForIndexing(db, workspaceId, documentId) {
|
|
1027
1397
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
1028
1398
|
const [row] = await scopedDb.select().from(schema.documents).where(
|
|
@@ -1033,12 +1403,8 @@ async function getDocumentForIndexing(db, workspaceId, documentId) {
|
|
|
1033
1403
|
}
|
|
1034
1404
|
async function queueDocumentForReindex(db, workspaceId, documentId, access, organizationAuthorityGranted) {
|
|
1035
1405
|
return await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1036
|
-
const [document] = await scopedDb.select(
|
|
1037
|
-
and(
|
|
1038
|
-
eq(schema.documents.workspaceId, workspaceId),
|
|
1039
|
-
eq(schema.documents.id, documentId),
|
|
1040
|
-
...documentAccessConditions(workspaceId, access)
|
|
1041
|
-
)
|
|
1406
|
+
const [document] = await scopedDb.select().from(schema.documents).where(
|
|
1407
|
+
and(eq(schema.documents.id, documentId), ...documentAccessConditions(workspaceId, access))
|
|
1042
1408
|
).limit(1);
|
|
1043
1409
|
if (!document) throw new Error(`Document not found: ${documentId}`);
|
|
1044
1410
|
assertOrganizationDocumentAuthority(document.authorityKind, organizationAuthorityGranted);
|
|
@@ -1047,11 +1413,7 @@ async function queueDocumentForReindex(db, workspaceId, documentId, access, orga
|
|
|
1047
1413
|
error: null,
|
|
1048
1414
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1049
1415
|
}).where(
|
|
1050
|
-
and(
|
|
1051
|
-
eq(schema.documents.workspaceId, workspaceId),
|
|
1052
|
-
eq(schema.documents.id, documentId),
|
|
1053
|
-
...documentAccessConditions(workspaceId, access)
|
|
1054
|
-
)
|
|
1416
|
+
and(eq(schema.documents.id, documentId), ...documentAccessConditions(workspaceId, access))
|
|
1055
1417
|
).returning();
|
|
1056
1418
|
if (!row) throw new Error(`Document not found: ${documentId}`);
|
|
1057
1419
|
return mapDocument(row);
|
|
@@ -1090,7 +1452,11 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
|
|
|
1090
1452
|
);
|
|
1091
1453
|
});
|
|
1092
1454
|
try {
|
|
1093
|
-
const
|
|
1455
|
+
const object = await retryWhileMissing(
|
|
1456
|
+
async () => objectStorage.getObjectBytes(file.objectKey)
|
|
1457
|
+
);
|
|
1458
|
+
if (!object) throw new Error("document source object is missing");
|
|
1459
|
+
const bytes = object.bytes;
|
|
1094
1460
|
const parsed = await services.parser.parse(bytes, file);
|
|
1095
1461
|
if (document.curationStatus === "pending") {
|
|
1096
1462
|
document = await curateDroppedDocument(db, services, document, parsed, file);
|
|
@@ -1284,6 +1650,9 @@ async function curateDroppedDocument(db, services, document, parsed, file) {
|
|
|
1284
1650
|
return updated ?? document;
|
|
1285
1651
|
}
|
|
1286
1652
|
async function searchDocuments(db, input, services = createDocumentServices()) {
|
|
1653
|
+
return (await searchDocumentCandidates(db, input, services)).results;
|
|
1654
|
+
}
|
|
1655
|
+
async function searchDocumentCandidates(db, input, services, relevanceFloor) {
|
|
1287
1656
|
await assertDocumentAccountWorkspace(db, input.accountId, input.workspaceId);
|
|
1288
1657
|
const mode = input.mode ?? "hybrid";
|
|
1289
1658
|
const limit = Math.min(Math.max(input.limit ?? 5, 1), 50);
|
|
@@ -1308,13 +1677,34 @@ async function searchDocuments(db, input, services = createDocumentServices()) {
|
|
|
1308
1677
|
if (mode === "keyword" || mode === "hybrid") {
|
|
1309
1678
|
rows.push(...await keywordSearchDocuments(db, input, candidateLimit));
|
|
1310
1679
|
}
|
|
1311
|
-
|
|
1680
|
+
const merged = mergeDocumentSearchRows(rows, mode);
|
|
1681
|
+
return selectDocumentSearchCandidateWindow(merged, limit, relevanceFloor);
|
|
1682
|
+
}
|
|
1683
|
+
function selectDocumentSearchCandidateWindow(merged, limit, relevanceFloor) {
|
|
1684
|
+
const boundedLimit = Math.min(Math.max(Math.trunc(limit), 1), 50);
|
|
1685
|
+
if (!relevanceFloor) {
|
|
1686
|
+
return { results: merged.slice(0, boundedLimit), belowRelevanceFloor: 0 };
|
|
1687
|
+
}
|
|
1688
|
+
let belowRelevanceFloor = 0;
|
|
1689
|
+
const relevant = merged.filter((result) => {
|
|
1690
|
+
const included = result.vectorScore !== null && result.vectorScore >= relevanceFloor.vectorScore || result.keywordScore !== null && result.keywordScore >= relevanceFloor.keywordScore;
|
|
1691
|
+
if (!included) belowRelevanceFloor += 1;
|
|
1692
|
+
return included;
|
|
1693
|
+
});
|
|
1694
|
+
return { results: relevant.slice(0, boundedLimit), belowRelevanceFloor };
|
|
1312
1695
|
}
|
|
1313
1696
|
async function searchEffectiveDocuments(db, input, services = createDocumentServices()) {
|
|
1314
1697
|
const initiatingSubjectId = cleanString(input.initiatingSubjectId);
|
|
1315
1698
|
if (!initiatingSubjectId) {
|
|
1316
1699
|
throw new Error("effective document retrieval requires an initiating subject");
|
|
1317
1700
|
}
|
|
1701
|
+
const access = await resolveEffectiveDocumentAccess(db, {
|
|
1702
|
+
accountId: input.accountId,
|
|
1703
|
+
workspaceId: input.workspaceId,
|
|
1704
|
+
initiatingSubjectId,
|
|
1705
|
+
surface: input.surface,
|
|
1706
|
+
agentAuthority: input.agentAuthority
|
|
1707
|
+
});
|
|
1318
1708
|
return await searchDocuments(
|
|
1319
1709
|
db,
|
|
1320
1710
|
{
|
|
@@ -1328,23 +1718,50 @@ async function searchEffectiveDocuments(db, input, services = createDocumentServ
|
|
|
1328
1718
|
aclTags: input.aclTags,
|
|
1329
1719
|
// Construct the lower-level access filter here instead of spreading the
|
|
1330
1720
|
// caller input, so an untyped/legacy access override is always ignored.
|
|
1331
|
-
access
|
|
1332
|
-
viewerSubjectId: initiatingSubjectId,
|
|
1333
|
-
...input.surface === "agent" ? { agentOnly: true } : {}
|
|
1334
|
-
}
|
|
1721
|
+
access
|
|
1335
1722
|
},
|
|
1336
1723
|
services
|
|
1337
1724
|
);
|
|
1338
1725
|
}
|
|
1339
1726
|
async function searchEffectiveKnowledge(db, input, services = createDocumentServices()) {
|
|
1340
1727
|
const initiatingSubjectId = canonicalEffectiveDocumentSubject(input.initiatingSubjectId);
|
|
1341
|
-
const
|
|
1728
|
+
const requestedLimit = Math.min(Math.max(input.limit ?? 5, 1), KNOWLEDGE_SEARCH_MAX_RESULTS);
|
|
1729
|
+
const access = await resolveEffectiveDocumentAccess(db, {
|
|
1730
|
+
accountId: input.accountId,
|
|
1731
|
+
workspaceId: input.workspaceId,
|
|
1732
|
+
initiatingSubjectId,
|
|
1733
|
+
surface: input.surface,
|
|
1734
|
+
agentAuthority: input.agentAuthority
|
|
1735
|
+
});
|
|
1736
|
+
const candidateLimit = Math.min(requestedLimit * 4, KNOWLEDGE_SEARCH_MAX_RESULTS);
|
|
1737
|
+
const rankedSelection = await searchDocumentCandidates(
|
|
1342
1738
|
db,
|
|
1343
|
-
{
|
|
1344
|
-
|
|
1739
|
+
{
|
|
1740
|
+
accountId: input.accountId,
|
|
1741
|
+
workspaceId: input.workspaceId,
|
|
1742
|
+
query: input.query,
|
|
1743
|
+
limit: candidateLimit,
|
|
1744
|
+
...input.baseIds ? { baseIds: input.baseIds } : {},
|
|
1745
|
+
...input.mode ? { mode: input.mode } : {},
|
|
1746
|
+
...input.sourceKinds ? { sourceKinds: input.sourceKinds } : {},
|
|
1747
|
+
...input.aclTags ? { aclTags: input.aclTags } : {},
|
|
1748
|
+
access
|
|
1749
|
+
},
|
|
1750
|
+
services,
|
|
1751
|
+
{
|
|
1752
|
+
vectorScore: KNOWLEDGE_SEARCH_MIN_VECTOR_SCORE,
|
|
1753
|
+
keywordScore: KNOWLEDGE_SEARCH_MIN_KEYWORD_SCORE
|
|
1754
|
+
}
|
|
1345
1755
|
);
|
|
1346
|
-
|
|
1347
|
-
|
|
1756
|
+
const ranked = rankedSelection.results;
|
|
1757
|
+
if (ranked.length === 0) {
|
|
1758
|
+
return selectKnowledgeSearchResults({
|
|
1759
|
+
candidates: [],
|
|
1760
|
+
rankedCandidateCount: 0,
|
|
1761
|
+
requestedLimit,
|
|
1762
|
+
alreadyBelowRelevanceFloor: rankedSelection.belowRelevanceFloor
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1348
1765
|
const current = await withDocumentAccountRls(
|
|
1349
1766
|
db,
|
|
1350
1767
|
input.accountId,
|
|
@@ -1353,7 +1770,9 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
|
|
|
1353
1770
|
async (scopedDb) => await scopedDb.select({
|
|
1354
1771
|
chunk: schema.documentChunks,
|
|
1355
1772
|
document: schema.documents,
|
|
1356
|
-
citation: googleDriveCitationProjection(input.workspaceId, access)
|
|
1773
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
1774
|
+
previousChunkId: knowledgePreviousChunkIdProjection(),
|
|
1775
|
+
nextChunkId: knowledgeNextChunkIdProjection()
|
|
1357
1776
|
}).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
|
|
1358
1777
|
and(
|
|
1359
1778
|
eq(schema.documents.accountId, input.accountId),
|
|
@@ -1368,28 +1787,310 @@ async function searchEffectiveKnowledge(db, input, services = createDocumentServ
|
|
|
1368
1787
|
)
|
|
1369
1788
|
);
|
|
1370
1789
|
const currentByChunkId = new Map(current.map((row) => [row.chunk.id, row]));
|
|
1371
|
-
return {
|
|
1372
|
-
|
|
1790
|
+
return selectKnowledgeSearchResults({
|
|
1791
|
+
rankedCandidateCount: ranked.length,
|
|
1792
|
+
requestedLimit,
|
|
1793
|
+
alreadyBelowRelevanceFloor: rankedSelection.belowRelevanceFloor,
|
|
1794
|
+
candidates: ranked.flatMap((rankedResult) => {
|
|
1373
1795
|
const row = currentByChunkId.get(rankedResult.chunkId);
|
|
1374
1796
|
if (!row) return [];
|
|
1375
1797
|
return [
|
|
1376
1798
|
{
|
|
1377
|
-
record: knowledgeChunkRecord(row.document, row.chunk, row.citation
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1799
|
+
record: knowledgeChunkRecord(row.document, row.chunk, row.citation, {
|
|
1800
|
+
previousChunkId: row.previousChunkId,
|
|
1801
|
+
nextChunkId: row.nextChunkId
|
|
1802
|
+
}),
|
|
1803
|
+
semanticScore: rankedResult.score,
|
|
1804
|
+
matchType: rankedResult.matchType,
|
|
1805
|
+
vectorScore: rankedResult.vectorScore,
|
|
1806
|
+
keywordScore: rankedResult.keywordScore
|
|
1384
1807
|
}
|
|
1385
1808
|
];
|
|
1386
1809
|
})
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
function selectKnowledgeSearchResults(input) {
|
|
1813
|
+
const requestedLimit = Math.min(
|
|
1814
|
+
Math.max(Math.trunc(input.requestedLimit), 1),
|
|
1815
|
+
KNOWLEDGE_SEARCH_MAX_RESULTS
|
|
1816
|
+
);
|
|
1817
|
+
const nowMs = (input.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
1818
|
+
let belowRelevanceFloor = Math.min(
|
|
1819
|
+
Math.max(0, Math.trunc(input.alreadyBelowRelevanceFloor ?? 0)),
|
|
1820
|
+
KNOWLEDGE_SEARCH_MAX_FLOOR_OMISSIONS
|
|
1821
|
+
);
|
|
1822
|
+
const relevant = [];
|
|
1823
|
+
for (const candidate of input.candidates) {
|
|
1824
|
+
const relevanceSignals = [];
|
|
1825
|
+
if (candidate.vectorScore !== null && candidate.vectorScore >= KNOWLEDGE_SEARCH_MIN_VECTOR_SCORE) {
|
|
1826
|
+
relevanceSignals.push("vector");
|
|
1827
|
+
}
|
|
1828
|
+
if (candidate.keywordScore !== null && candidate.keywordScore >= KNOWLEDGE_SEARCH_MIN_KEYWORD_SCORE) {
|
|
1829
|
+
relevanceSignals.push("keyword");
|
|
1830
|
+
}
|
|
1831
|
+
if (relevanceSignals.length === 0) {
|
|
1832
|
+
belowRelevanceFloor = Math.min(belowRelevanceFloor + 1, KNOWLEDGE_SEARCH_MAX_FLOOR_OMISSIONS);
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
const freshness = knowledgeFreshness(candidate.record.quality.freshnessAt, nowMs);
|
|
1836
|
+
const qualityAdjustment = freshness === "current" ? 0.02 : freshness === "aging" ? 0.01 : 0;
|
|
1837
|
+
relevant.push({
|
|
1838
|
+
record: candidate.record,
|
|
1839
|
+
retrieval: {
|
|
1840
|
+
score: roundScore(Math.min(1, candidate.semanticScore + qualityAdjustment)),
|
|
1841
|
+
semanticScore: roundScore(candidate.semanticScore),
|
|
1842
|
+
matchType: candidate.matchType,
|
|
1843
|
+
vectorScore: candidate.vectorScore === null ? null : roundScore(candidate.vectorScore),
|
|
1844
|
+
keywordScore: candidate.keywordScore === null ? null : roundScore(candidate.keywordScore),
|
|
1845
|
+
relevanceSignals,
|
|
1846
|
+
freshness,
|
|
1847
|
+
qualityAdjustment,
|
|
1848
|
+
duplicateCount: 0
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
relevant.sort(compareKnowledgeSearchResults);
|
|
1853
|
+
const deduped = [];
|
|
1854
|
+
const byContent = /* @__PURE__ */ new Map();
|
|
1855
|
+
let asDuplicate = 0;
|
|
1856
|
+
for (const result of relevant) {
|
|
1857
|
+
const key = knowledgeTextualContentKey(result.record);
|
|
1858
|
+
const retainedIndex = byContent.get(key);
|
|
1859
|
+
if (retainedIndex === void 0) {
|
|
1860
|
+
byContent.set(key, deduped.length);
|
|
1861
|
+
deduped.push(result);
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
asDuplicate += 1;
|
|
1865
|
+
const retained = deduped[retainedIndex];
|
|
1866
|
+
retained.retrieval.duplicateCount += 1;
|
|
1867
|
+
}
|
|
1868
|
+
const forLimit = Math.max(0, deduped.length - requestedLimit);
|
|
1869
|
+
const bounded = deduped.slice(0, requestedLimit);
|
|
1870
|
+
let forResponseBudget = 0;
|
|
1871
|
+
let response = knowledgeSearchResponse({
|
|
1872
|
+
results: bounded,
|
|
1873
|
+
rankedCandidateCount: input.rankedCandidateCount,
|
|
1874
|
+
recheckedCandidateCount: input.candidates.length,
|
|
1875
|
+
belowRelevanceFloor,
|
|
1876
|
+
asDuplicate,
|
|
1877
|
+
forLimit,
|
|
1878
|
+
forResponseBudget
|
|
1879
|
+
});
|
|
1880
|
+
while (knowledgeResponseBytes(response) > KNOWLEDGE_SEARCH_MAX_RESPONSE_BYTES) {
|
|
1881
|
+
if (bounded.length === 0) {
|
|
1882
|
+
throw new Error("knowledge search selection facts exceed the response budget");
|
|
1883
|
+
}
|
|
1884
|
+
bounded.pop();
|
|
1885
|
+
forResponseBudget += 1;
|
|
1886
|
+
response = knowledgeSearchResponse({
|
|
1887
|
+
results: bounded,
|
|
1888
|
+
rankedCandidateCount: input.rankedCandidateCount,
|
|
1889
|
+
recheckedCandidateCount: input.candidates.length,
|
|
1890
|
+
belowRelevanceFloor,
|
|
1891
|
+
asDuplicate,
|
|
1892
|
+
forLimit,
|
|
1893
|
+
forResponseBudget
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
return response;
|
|
1897
|
+
}
|
|
1898
|
+
function compareKnowledgeSearchResults(left, right) {
|
|
1899
|
+
return right.retrieval.score - left.retrieval.score || right.retrieval.semanticScore - left.retrieval.semanticScore || (right.retrieval.vectorScore ?? 0) - (left.retrieval.vectorScore ?? 0) || (right.retrieval.keywordScore ?? 0) - (left.retrieval.keywordScore ?? 0) || (left.record.id === right.record.id ? 0 : left.record.id < right.record.id ? -1 : 1);
|
|
1900
|
+
}
|
|
1901
|
+
function knowledgeFreshness(value, nowMs) {
|
|
1902
|
+
const freshnessMs = Date.parse(value);
|
|
1903
|
+
if (!Number.isFinite(freshnessMs)) return "stale";
|
|
1904
|
+
const ageDays = Math.max(0, (nowMs - freshnessMs) / 864e5);
|
|
1905
|
+
if (ageDays <= 90) return "current";
|
|
1906
|
+
return ageDays <= 365 ? "aging" : "stale";
|
|
1907
|
+
}
|
|
1908
|
+
function knowledgeTextualContentKey(record) {
|
|
1909
|
+
return createHash("sha256").update("opengeni:knowledge-search-content:v1\0").update(record.title).update("\0").update(
|
|
1910
|
+
JSON.stringify({
|
|
1911
|
+
body: record.content.body,
|
|
1912
|
+
summary: record.content.summary,
|
|
1913
|
+
topics: record.content.topics
|
|
1914
|
+
})
|
|
1915
|
+
).digest("hex");
|
|
1916
|
+
}
|
|
1917
|
+
function knowledgeSearchResponse(input) {
|
|
1918
|
+
const selection = {
|
|
1919
|
+
relevanceFloor: {
|
|
1920
|
+
policy: "any_signal",
|
|
1921
|
+
vectorScore: KNOWLEDGE_SEARCH_MIN_VECTOR_SCORE,
|
|
1922
|
+
keywordScore: KNOWLEDGE_SEARCH_MIN_KEYWORD_SCORE
|
|
1923
|
+
},
|
|
1924
|
+
dedupe: { policy: "exact_textual_content" },
|
|
1925
|
+
candidates: {
|
|
1926
|
+
ranked: input.rankedCandidateCount,
|
|
1927
|
+
rechecked: input.recheckedCandidateCount,
|
|
1928
|
+
omittedOnRecheck: Math.max(0, input.rankedCandidateCount - input.recheckedCandidateCount)
|
|
1929
|
+
},
|
|
1930
|
+
omitted: {
|
|
1931
|
+
belowRelevanceFloor: input.belowRelevanceFloor,
|
|
1932
|
+
asDuplicate: input.asDuplicate,
|
|
1933
|
+
forLimit: input.forLimit,
|
|
1934
|
+
forResponseBudget: input.forResponseBudget
|
|
1935
|
+
},
|
|
1936
|
+
budget: {
|
|
1937
|
+
maxResults: KNOWLEDGE_SEARCH_MAX_RESULTS,
|
|
1938
|
+
maxResponseBytes: KNOWLEDGE_SEARCH_MAX_RESPONSE_BYTES,
|
|
1939
|
+
responseBytes: 0,
|
|
1940
|
+
tokenEstimateBytesPerToken: KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN,
|
|
1941
|
+
estimatedTokens: 0,
|
|
1942
|
+
maxEstimatedTokens: Math.ceil(
|
|
1943
|
+
KNOWLEDGE_SEARCH_MAX_RESPONSE_BYTES / KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN
|
|
1944
|
+
)
|
|
1945
|
+
}
|
|
1387
1946
|
};
|
|
1947
|
+
const response = {
|
|
1948
|
+
results: [...input.results],
|
|
1949
|
+
selection
|
|
1950
|
+
};
|
|
1951
|
+
for (let index = 0; index < 8; index += 1) {
|
|
1952
|
+
const responseBytes = knowledgeResponseBytes(response);
|
|
1953
|
+
const estimatedTokens = Math.ceil(
|
|
1954
|
+
responseBytes / KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN
|
|
1955
|
+
);
|
|
1956
|
+
if (response.selection.budget.responseBytes === responseBytes && response.selection.budget.estimatedTokens === estimatedTokens) {
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
response.selection.budget.responseBytes = responseBytes;
|
|
1960
|
+
response.selection.budget.estimatedTokens = estimatedTokens;
|
|
1961
|
+
}
|
|
1962
|
+
return response;
|
|
1963
|
+
}
|
|
1964
|
+
function knowledgeResponseBytes(response) {
|
|
1965
|
+
return Buffer.byteLength(JSON.stringify(response), "utf8");
|
|
1966
|
+
}
|
|
1967
|
+
function selectKnowledgeBrowseRecords(input) {
|
|
1968
|
+
const entries = input.entries.slice(0, KNOWLEDGE_BROWSE_MAX_LIMIT);
|
|
1969
|
+
const selected = entries.map((entry) => entry.record);
|
|
1970
|
+
let omittedForResponseBudget = 0;
|
|
1971
|
+
let compactedRecordCount = 0;
|
|
1972
|
+
let response = knowledgeBrowseResponse({
|
|
1973
|
+
records: selected,
|
|
1974
|
+
nextCursor: input.hasMoreAfterEntries ? entries.at(-1)?.cursorAfter ?? null : null,
|
|
1975
|
+
hasMore: input.hasMoreAfterEntries,
|
|
1976
|
+
omittedForResponseBudget,
|
|
1977
|
+
compactedRecordCount
|
|
1978
|
+
});
|
|
1979
|
+
while (selected.length > 1 && knowledgeBrowseResponseBytes(response) > KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES) {
|
|
1980
|
+
selected.pop();
|
|
1981
|
+
omittedForResponseBudget += 1;
|
|
1982
|
+
response = knowledgeBrowseResponse({
|
|
1983
|
+
records: selected,
|
|
1984
|
+
nextCursor: entries[selected.length - 1]?.cursorAfter ?? null,
|
|
1985
|
+
hasMore: true,
|
|
1986
|
+
omittedForResponseBudget,
|
|
1987
|
+
compactedRecordCount
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
if (selected.length === 1 && knowledgeBrowseResponseBytes(response) > KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES) {
|
|
1991
|
+
selected[0] = compactKnowledgeBrowseRecord(selected[0]);
|
|
1992
|
+
compactedRecordCount = 1;
|
|
1993
|
+
response = knowledgeBrowseResponse({
|
|
1994
|
+
records: selected,
|
|
1995
|
+
nextCursor: omittedForResponseBudget > 0 || input.hasMoreAfterEntries ? entries[0]?.cursorAfter ?? null : null,
|
|
1996
|
+
hasMore: omittedForResponseBudget > 0 || input.hasMoreAfterEntries,
|
|
1997
|
+
omittedForResponseBudget,
|
|
1998
|
+
compactedRecordCount
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
if (knowledgeBrowseResponseBytes(response) > KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES) {
|
|
2002
|
+
throw new Error("knowledge browse discovery projection exceeds the response budget");
|
|
2003
|
+
}
|
|
2004
|
+
return response;
|
|
2005
|
+
}
|
|
2006
|
+
function compactKnowledgeBrowseRecord(record) {
|
|
2007
|
+
const fields = /* @__PURE__ */ new Set([
|
|
2008
|
+
...record.projection.fields,
|
|
2009
|
+
"content.body",
|
|
2010
|
+
"content.summary",
|
|
2011
|
+
"content.topics",
|
|
2012
|
+
"content.metadata",
|
|
2013
|
+
"provenance.source.uri",
|
|
2014
|
+
"provenance.source.externalId",
|
|
2015
|
+
"provenance.source.title",
|
|
2016
|
+
"provenance.source.author",
|
|
2017
|
+
"provenance.source.version",
|
|
2018
|
+
"provenance.citation"
|
|
2019
|
+
]);
|
|
2020
|
+
return {
|
|
2021
|
+
...record,
|
|
2022
|
+
content: {
|
|
2023
|
+
format: "markdown",
|
|
2024
|
+
body: null,
|
|
2025
|
+
summary: null,
|
|
2026
|
+
topics: [],
|
|
2027
|
+
metadata: {}
|
|
2028
|
+
},
|
|
2029
|
+
provenance: {
|
|
2030
|
+
...record.provenance,
|
|
2031
|
+
source: {
|
|
2032
|
+
...record.provenance.source,
|
|
2033
|
+
uri: null,
|
|
2034
|
+
externalId: null,
|
|
2035
|
+
title: null,
|
|
2036
|
+
author: null,
|
|
2037
|
+
version: null
|
|
2038
|
+
},
|
|
2039
|
+
citation: null
|
|
2040
|
+
},
|
|
2041
|
+
links: record.links.filter((link) => link.target.kind === "knowledge"),
|
|
2042
|
+
projection: {
|
|
2043
|
+
truncated: true,
|
|
2044
|
+
fields: [...fields].sort()
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
function knowledgeBrowseResponse(input) {
|
|
2049
|
+
const response = {
|
|
2050
|
+
records: [...input.records],
|
|
2051
|
+
nextCursor: input.nextCursor,
|
|
2052
|
+
hasMore: input.hasMore,
|
|
2053
|
+
selection: {
|
|
2054
|
+
omitted: { forResponseBudget: input.omittedForResponseBudget },
|
|
2055
|
+
compactedRecordCount: input.compactedRecordCount,
|
|
2056
|
+
budget: {
|
|
2057
|
+
maxResults: KNOWLEDGE_BROWSE_MAX_LIMIT,
|
|
2058
|
+
maxResponseBytes: KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES,
|
|
2059
|
+
responseBytes: 0,
|
|
2060
|
+
tokenEstimateBytesPerToken: KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN,
|
|
2061
|
+
estimatedTokens: 0,
|
|
2062
|
+
maxEstimatedTokens: Math.ceil(
|
|
2063
|
+
KNOWLEDGE_BROWSE_MAX_RESPONSE_BYTES / KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN
|
|
2064
|
+
)
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
for (let index = 0; index < 8; index += 1) {
|
|
2069
|
+
const responseBytes = knowledgeBrowseResponseBytes(response);
|
|
2070
|
+
const estimatedTokens = Math.ceil(
|
|
2071
|
+
responseBytes / KNOWLEDGE_SEARCH_TOKEN_ESTIMATE_BYTES_PER_TOKEN
|
|
2072
|
+
);
|
|
2073
|
+
if (response.selection.budget.responseBytes === responseBytes && response.selection.budget.estimatedTokens === estimatedTokens) {
|
|
2074
|
+
break;
|
|
2075
|
+
}
|
|
2076
|
+
response.selection.budget.responseBytes = responseBytes;
|
|
2077
|
+
response.selection.budget.estimatedTokens = estimatedTokens;
|
|
2078
|
+
}
|
|
2079
|
+
return response;
|
|
2080
|
+
}
|
|
2081
|
+
function knowledgeBrowseResponseBytes(response) {
|
|
2082
|
+
return Buffer.byteLength(JSON.stringify(response), "utf8");
|
|
1388
2083
|
}
|
|
1389
2084
|
async function getEffectiveKnowledgeRecord(db, input) {
|
|
1390
2085
|
const initiatingSubjectId = canonicalEffectiveDocumentSubject(input.initiatingSubjectId);
|
|
1391
2086
|
const target = parseKnowledgeRecordId(input.id);
|
|
1392
|
-
const access =
|
|
2087
|
+
const access = await resolveEffectiveDocumentAccess(db, {
|
|
2088
|
+
accountId: input.accountId,
|
|
2089
|
+
workspaceId: input.workspaceId,
|
|
2090
|
+
initiatingSubjectId,
|
|
2091
|
+
surface: input.surface ?? "agent",
|
|
2092
|
+
agentAuthority: input.agentAuthority
|
|
2093
|
+
});
|
|
1393
2094
|
return await withDocumentAccountRls(
|
|
1394
2095
|
db,
|
|
1395
2096
|
input.accountId,
|
|
@@ -1399,8 +2100,16 @@ async function getEffectiveKnowledgeRecord(db, input) {
|
|
|
1399
2100
|
if (target.kind === "document") {
|
|
1400
2101
|
const [row2] = await scopedDb.select({
|
|
1401
2102
|
document: schema.documents,
|
|
1402
|
-
citation: googleDriveCitationProjection(input.workspaceId, access)
|
|
1403
|
-
|
|
2103
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2104
|
+
firstChunkId: schema.documentChunks.id
|
|
2105
|
+
}).from(schema.documents).leftJoin(
|
|
2106
|
+
schema.documentChunks,
|
|
2107
|
+
and(
|
|
2108
|
+
eq(schema.documentChunks.accountId, schema.documents.accountId),
|
|
2109
|
+
eq(schema.documentChunks.documentId, schema.documents.id),
|
|
2110
|
+
eq(schema.documentChunks.chunkIndex, 0)
|
|
2111
|
+
)
|
|
2112
|
+
).where(
|
|
1404
2113
|
and(
|
|
1405
2114
|
eq(schema.documents.accountId, input.accountId),
|
|
1406
2115
|
eq(schema.documents.id, target.id),
|
|
@@ -1408,12 +2117,14 @@ async function getEffectiveKnowledgeRecord(db, input) {
|
|
|
1408
2117
|
...documentAccessConditions(input.workspaceId, access)
|
|
1409
2118
|
)
|
|
1410
2119
|
).limit(1);
|
|
1411
|
-
return row2 ? knowledgeDocumentRecord(row2.document, row2.citation) : null;
|
|
2120
|
+
return row2 ? knowledgeDocumentRecord(row2.document, row2.citation, row2.firstChunkId) : null;
|
|
1412
2121
|
}
|
|
1413
2122
|
const [row] = await scopedDb.select({
|
|
1414
2123
|
chunk: schema.documentChunks,
|
|
1415
2124
|
document: schema.documents,
|
|
1416
|
-
citation: googleDriveCitationProjection(input.workspaceId, access)
|
|
2125
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2126
|
+
previousChunkId: knowledgePreviousChunkIdProjection(),
|
|
2127
|
+
nextChunkId: knowledgeNextChunkIdProjection()
|
|
1417
2128
|
}).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
|
|
1418
2129
|
and(
|
|
1419
2130
|
eq(schema.documents.accountId, input.accountId),
|
|
@@ -1423,7 +2134,10 @@ async function getEffectiveKnowledgeRecord(db, input) {
|
|
|
1423
2134
|
...documentAccessConditions(input.workspaceId, access)
|
|
1424
2135
|
)
|
|
1425
2136
|
).limit(1);
|
|
1426
|
-
return row ? knowledgeChunkRecord(row.document, row.chunk, row.citation
|
|
2137
|
+
return row ? knowledgeChunkRecord(row.document, row.chunk, row.citation, {
|
|
2138
|
+
previousChunkId: row.previousChunkId,
|
|
2139
|
+
nextChunkId: row.nextChunkId
|
|
2140
|
+
}) : null;
|
|
1427
2141
|
}
|
|
1428
2142
|
);
|
|
1429
2143
|
}
|
|
@@ -1453,11 +2167,14 @@ async function browseEffectiveKnowledge(db, input) {
|
|
|
1453
2167
|
topic,
|
|
1454
2168
|
sourceKinds
|
|
1455
2169
|
};
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
2170
|
+
const topLevelAfter = !parent && input.cursor ? decodeKnowledgeBrowseCursor(input.cursor, cursorScope) : 0n;
|
|
2171
|
+
const access = await resolveEffectiveDocumentAccess(db, {
|
|
2172
|
+
accountId: input.accountId,
|
|
2173
|
+
workspaceId: input.workspaceId,
|
|
2174
|
+
initiatingSubjectId,
|
|
2175
|
+
surface: input.surface ?? "agent",
|
|
2176
|
+
agentAuthority: input.agentAuthority
|
|
2177
|
+
});
|
|
1461
2178
|
return await withDocumentAccountRls(
|
|
1462
2179
|
db,
|
|
1463
2180
|
input.accountId,
|
|
@@ -1465,7 +2182,7 @@ async function browseEffectiveKnowledge(db, input) {
|
|
|
1465
2182
|
access,
|
|
1466
2183
|
async (scopedDb) => {
|
|
1467
2184
|
if (parent) {
|
|
1468
|
-
const [authorizedParent] = await scopedDb.select({ id: schema.documents.id }).from(schema.documents).where(
|
|
2185
|
+
const [authorizedParent] = await scopedDb.select({ id: schema.documents.id, indexSequence: schema.documents.indexSequence }).from(schema.documents).where(
|
|
1469
2186
|
and(
|
|
1470
2187
|
eq(schema.documents.accountId, input.accountId),
|
|
1471
2188
|
eq(schema.documents.id, parent.id),
|
|
@@ -1473,34 +2190,57 @@ async function browseEffectiveKnowledge(db, input) {
|
|
|
1473
2190
|
...documentAccessConditions(input.workspaceId, access)
|
|
1474
2191
|
)
|
|
1475
2192
|
).limit(1);
|
|
1476
|
-
if (!authorizedParent)
|
|
2193
|
+
if (!authorizedParent) {
|
|
2194
|
+
return selectKnowledgeBrowseRecords({ entries: [], hasMoreAfterEntries: false });
|
|
2195
|
+
}
|
|
2196
|
+
if (authorizedParent.indexSequence === null) {
|
|
2197
|
+
throw new Error("ready knowledge document is missing its index revision");
|
|
2198
|
+
}
|
|
2199
|
+
const parentCursorScope = {
|
|
2200
|
+
...cursorScope,
|
|
2201
|
+
parentRevision: authorizedParent.indexSequence.toString()
|
|
2202
|
+
};
|
|
2203
|
+
const after = input.cursor ? decodeKnowledgeBrowseCursor(input.cursor, parentCursorScope) : 0n;
|
|
2204
|
+
if (after > 2147483648n) {
|
|
2205
|
+
throw new Error("invalid knowledge browse cursor");
|
|
2206
|
+
}
|
|
1477
2207
|
const rows2 = await scopedDb.select({
|
|
1478
2208
|
chunk: schema.documentChunks,
|
|
1479
2209
|
document: schema.documents,
|
|
1480
|
-
citation: googleDriveCitationProjection(input.workspaceId, access)
|
|
2210
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2211
|
+
previousChunkId: knowledgePreviousChunkIdProjection(),
|
|
2212
|
+
nextChunkId: knowledgeNextChunkIdProjection()
|
|
1481
2213
|
}).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
|
|
1482
2214
|
and(
|
|
1483
2215
|
eq(schema.documentChunks.accountId, input.accountId),
|
|
1484
2216
|
eq(schema.documentChunks.documentId, parent.id),
|
|
1485
2217
|
gt(schema.documentChunks.chunkIndex, Number(after) - 1),
|
|
1486
2218
|
eq(schema.documents.status, "ready"),
|
|
2219
|
+
eq(schema.documents.indexSequence, authorizedParent.indexSequence),
|
|
1487
2220
|
...documentAccessConditions(input.workspaceId, access)
|
|
1488
2221
|
)
|
|
1489
2222
|
).orderBy(asc(schema.documentChunks.chunkIndex)).limit(limit + 1);
|
|
1490
2223
|
const hasMore2 = rows2.length > limit;
|
|
1491
2224
|
const page2 = rows2.slice(0, limit);
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
2225
|
+
return selectKnowledgeBrowseRecords({
|
|
2226
|
+
entries: page2.map((row) => ({
|
|
2227
|
+
record: knowledgeChunkRecord(row.document, row.chunk, row.citation, {
|
|
2228
|
+
previousChunkId: row.previousChunkId,
|
|
2229
|
+
nextChunkId: row.nextChunkId
|
|
2230
|
+
}),
|
|
2231
|
+
cursorAfter: encodeKnowledgeBrowseCursor(
|
|
2232
|
+
parentCursorScope,
|
|
2233
|
+
BigInt(row.chunk.chunkIndex + 1)
|
|
2234
|
+
)
|
|
2235
|
+
})),
|
|
2236
|
+
hasMoreAfterEntries: hasMore2
|
|
2237
|
+
});
|
|
1498
2238
|
}
|
|
1499
2239
|
const conditions = [
|
|
1500
2240
|
eq(schema.documents.accountId, input.accountId),
|
|
1501
2241
|
eq(schema.documents.status, "ready"),
|
|
1502
2242
|
isNotNull(schema.documents.indexSequence),
|
|
1503
|
-
gt(schema.documents.indexSequence,
|
|
2243
|
+
gt(schema.documents.indexSequence, topLevelAfter),
|
|
1504
2244
|
...documentAccessConditions(input.workspaceId, access)
|
|
1505
2245
|
];
|
|
1506
2246
|
if (topic) conditions.push(sql`${schema.documents.topics} ? ${topic}`);
|
|
@@ -1508,23 +2248,38 @@ async function browseEffectiveKnowledge(db, input) {
|
|
|
1508
2248
|
conditions.push(inArray(schema.documents.sourceKind, sourceKinds));
|
|
1509
2249
|
const rows = await scopedDb.select({
|
|
1510
2250
|
document: schema.documents,
|
|
1511
|
-
citation: googleDriveCitationProjection(input.workspaceId, access)
|
|
1512
|
-
|
|
2251
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2252
|
+
firstChunkId: schema.documentChunks.id
|
|
2253
|
+
}).from(schema.documents).leftJoin(
|
|
2254
|
+
schema.documentChunks,
|
|
2255
|
+
and(
|
|
2256
|
+
eq(schema.documentChunks.accountId, schema.documents.accountId),
|
|
2257
|
+
eq(schema.documentChunks.documentId, schema.documents.id),
|
|
2258
|
+
eq(schema.documentChunks.chunkIndex, 0)
|
|
2259
|
+
)
|
|
2260
|
+
).where(and(...conditions)).orderBy(asc(schema.documents.indexSequence)).limit(limit + 1);
|
|
1513
2261
|
const hasMore = rows.length > limit;
|
|
1514
2262
|
const page = rows.slice(0, limit);
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2263
|
+
return selectKnowledgeBrowseRecords({
|
|
2264
|
+
entries: page.map((row) => {
|
|
2265
|
+
if (row.document.indexSequence === null) {
|
|
2266
|
+
throw new Error("ready knowledge document is missing its index revision");
|
|
2267
|
+
}
|
|
2268
|
+
return {
|
|
2269
|
+
record: knowledgeDocumentRecord(row.document, row.citation, row.firstChunkId),
|
|
2270
|
+
cursorAfter: encodeKnowledgeBrowseCursor(cursorScope, row.document.indexSequence)
|
|
2271
|
+
};
|
|
2272
|
+
}),
|
|
2273
|
+
hasMoreAfterEntries: hasMore
|
|
2274
|
+
});
|
|
1521
2275
|
}
|
|
1522
2276
|
);
|
|
1523
2277
|
}
|
|
1524
2278
|
function encodeKnowledgeBrowseCursor(scope, position) {
|
|
1525
2279
|
if (position < 0n) throw new Error("knowledge browse cursor position is invalid");
|
|
2280
|
+
const version = knowledgeBrowseCursorVersion(scope);
|
|
1526
2281
|
return Buffer.from(
|
|
1527
|
-
JSON.stringify({ v:
|
|
2282
|
+
JSON.stringify({ v: version, s: knowledgeBrowseCursorScope(scope), q: position.toString() }),
|
|
1528
2283
|
"utf8"
|
|
1529
2284
|
).toString("base64url");
|
|
1530
2285
|
}
|
|
@@ -1536,7 +2291,8 @@ function decodeKnowledgeBrowseCursor(value, scope) {
|
|
|
1536
2291
|
const bytes = Buffer.from(value, "base64url");
|
|
1537
2292
|
if (bytes.toString("base64url") !== value) throw new Error("cursor encoding");
|
|
1538
2293
|
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
1539
|
-
|
|
2294
|
+
const expectedVersion = knowledgeBrowseCursorVersion(scope);
|
|
2295
|
+
if (Object.keys(parsed).sort().join(",") !== "q,s,v" || parsed.v !== expectedVersion || typeof parsed.s !== "string" || typeof parsed.q !== "string" || !/^(0|[1-9][0-9]*)$/.test(parsed.q)) {
|
|
1540
2296
|
throw new Error("cursor payload");
|
|
1541
2297
|
}
|
|
1542
2298
|
if (parsed.s !== knowledgeBrowseCursorScope(scope)) {
|
|
@@ -1553,7 +2309,17 @@ function decodeKnowledgeBrowseCursor(value, scope) {
|
|
|
1553
2309
|
}
|
|
1554
2310
|
}
|
|
1555
2311
|
function knowledgeBrowseCursorScope(scope) {
|
|
1556
|
-
|
|
2312
|
+
const version = knowledgeBrowseCursorVersion(scope);
|
|
2313
|
+
const hash = createHash("sha256").update(`opengeni:knowledge-browse-cursor:v${version}\0`).update(scope.accountId).update("\0").update(scope.workspaceId).update("\0").update(canonicalEffectiveDocumentSubject(scope.initiatingSubjectId)).update("\0").update(scope.parentId ?? "").update("\0");
|
|
2314
|
+
if (version === 2) hash.update(scope.parentRevision).update("\0");
|
|
2315
|
+
return hash.update(scope.topic ?? "").update("\0").update([...scope.sourceKinds].sort().join("\0")).digest("hex");
|
|
2316
|
+
}
|
|
2317
|
+
function knowledgeBrowseCursorVersion(scope) {
|
|
2318
|
+
if (!scope.parentId) return 1;
|
|
2319
|
+
if (!scope.parentRevision || !/^[1-9][0-9]*$/.test(scope.parentRevision)) {
|
|
2320
|
+
throw new Error("knowledge browse parent cursor requires an exact document revision");
|
|
2321
|
+
}
|
|
2322
|
+
return 2;
|
|
1557
2323
|
}
|
|
1558
2324
|
function parseKnowledgeRecordId(value) {
|
|
1559
2325
|
const match = /^(document|document_chunk):([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/iu.exec(
|
|
@@ -1562,7 +2328,7 @@ function parseKnowledgeRecordId(value) {
|
|
|
1562
2328
|
if (!match) throw new Error("invalid knowledge record id");
|
|
1563
2329
|
return { kind: match[1], id: match[2].toLowerCase() };
|
|
1564
2330
|
}
|
|
1565
|
-
function knowledgeDocumentRecord(document, citation = null) {
|
|
2331
|
+
function knowledgeDocumentRecord(document, citation = null, firstChunkId = null) {
|
|
1566
2332
|
if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
|
|
1567
2333
|
const projected = projectKnowledgeRecord({
|
|
1568
2334
|
title: document.title,
|
|
@@ -1585,11 +2351,22 @@ function knowledgeDocumentRecord(document, citation = null) {
|
|
|
1585
2351
|
},
|
|
1586
2352
|
lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
|
|
1587
2353
|
quality: knowledgeQuality(document),
|
|
1588
|
-
links:
|
|
2354
|
+
links: [
|
|
2355
|
+
...firstChunkId ? [
|
|
2356
|
+
{
|
|
2357
|
+
relation: "contents",
|
|
2358
|
+
target: {
|
|
2359
|
+
kind: "knowledge",
|
|
2360
|
+
id: `document_chunk:${firstChunkId}`
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
] : [],
|
|
2364
|
+
...knowledgeSourceLinks(projected.source.uri)
|
|
2365
|
+
],
|
|
1589
2366
|
projection: projected.projection
|
|
1590
2367
|
};
|
|
1591
2368
|
}
|
|
1592
|
-
function knowledgeChunkRecord(document, chunk, citation = null) {
|
|
2369
|
+
function knowledgeChunkRecord(document, chunk, citation = null, traversal = { previousChunkId: null, nextChunkId: null }) {
|
|
1593
2370
|
if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
|
|
1594
2371
|
const projected = projectKnowledgeRecord({
|
|
1595
2372
|
title: document.title,
|
|
@@ -1613,7 +2390,28 @@ function knowledgeChunkRecord(document, chunk, citation = null) {
|
|
|
1613
2390
|
lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
|
|
1614
2391
|
quality: knowledgeQuality(document),
|
|
1615
2392
|
links: [
|
|
1616
|
-
{
|
|
2393
|
+
{
|
|
2394
|
+
relation: "parent",
|
|
2395
|
+
target: { kind: "knowledge", id: `document:${document.id}` }
|
|
2396
|
+
},
|
|
2397
|
+
...traversal.previousChunkId ? [
|
|
2398
|
+
{
|
|
2399
|
+
relation: "previous",
|
|
2400
|
+
target: {
|
|
2401
|
+
kind: "knowledge",
|
|
2402
|
+
id: `document_chunk:${traversal.previousChunkId}`
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
] : [],
|
|
2406
|
+
...traversal.nextChunkId ? [
|
|
2407
|
+
{
|
|
2408
|
+
relation: "next",
|
|
2409
|
+
target: {
|
|
2410
|
+
kind: "knowledge",
|
|
2411
|
+
id: `document_chunk:${traversal.nextChunkId}`
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
] : [],
|
|
1617
2415
|
...knowledgeSourceLinks(projected.source.uri)
|
|
1618
2416
|
],
|
|
1619
2417
|
projection: projected.projection
|
|
@@ -1643,6 +2441,28 @@ function knowledgeQuality(document) {
|
|
|
1643
2441
|
function knowledgeSourceLinks(sourceUri) {
|
|
1644
2442
|
return sourceUri ? [{ relation: "source", target: { kind: "external", uri: sourceUri } }] : [];
|
|
1645
2443
|
}
|
|
2444
|
+
function knowledgePreviousChunkIdProjection() {
|
|
2445
|
+
return sql`(
|
|
2446
|
+
select knowledge_previous_chunk.id
|
|
2447
|
+
from document_chunks knowledge_previous_chunk
|
|
2448
|
+
where knowledge_previous_chunk.account_id = ${schema.documentChunks.accountId}
|
|
2449
|
+
and knowledge_previous_chunk.document_id = ${schema.documentChunks.documentId}
|
|
2450
|
+
and knowledge_previous_chunk.chunk_index < ${schema.documentChunks.chunkIndex}
|
|
2451
|
+
order by knowledge_previous_chunk.chunk_index desc
|
|
2452
|
+
limit 1
|
|
2453
|
+
)`;
|
|
2454
|
+
}
|
|
2455
|
+
function knowledgeNextChunkIdProjection() {
|
|
2456
|
+
return sql`(
|
|
2457
|
+
select knowledge_next_chunk.id
|
|
2458
|
+
from document_chunks knowledge_next_chunk
|
|
2459
|
+
where knowledge_next_chunk.account_id = ${schema.documentChunks.accountId}
|
|
2460
|
+
and knowledge_next_chunk.document_id = ${schema.documentChunks.documentId}
|
|
2461
|
+
and knowledge_next_chunk.chunk_index > ${schema.documentChunks.chunkIndex}
|
|
2462
|
+
order by knowledge_next_chunk.chunk_index asc
|
|
2463
|
+
limit 1
|
|
2464
|
+
)`;
|
|
2465
|
+
}
|
|
1646
2466
|
async function vectorSearchDocuments(db, input, limit, services) {
|
|
1647
2467
|
const queryEmbedding = await services.embedder.embedQuery(input.query);
|
|
1648
2468
|
validateEmbedding(queryEmbedding, services.embedder.dimensions, services.embedder.model);
|
|
@@ -1676,7 +2496,7 @@ async function vectorSearchDocuments(db, input, limit, services) {
|
|
|
1676
2496
|
authoritySubjectId: schema.documents.authoritySubjectId,
|
|
1677
2497
|
citation: googleDriveCitationProjection(input.workspaceId, input.access),
|
|
1678
2498
|
distance
|
|
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)
|
|
2499
|
+
}).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(and(...documentSearchConditions(input, services.embedder.model))).orderBy(distance, asc(schema.documentChunks.id)).limit(limit)
|
|
1680
2500
|
);
|
|
1681
2501
|
return rows.map((row) => ({
|
|
1682
2502
|
...mapSearchRowBase(row),
|
|
@@ -1720,7 +2540,7 @@ async function keywordSearchDocuments(db, input, limit) {
|
|
|
1720
2540
|
...documentSearchConditions(input),
|
|
1721
2541
|
sql`to_tsvector('simple', ${schema.documentChunks.text}) @@ plainto_tsquery('simple', ${input.query})`
|
|
1722
2542
|
)
|
|
1723
|
-
).orderBy(desc(rank)).limit(limit)
|
|
2543
|
+
).orderBy(desc(rank), asc(schema.documentChunks.id)).limit(limit)
|
|
1724
2544
|
);
|
|
1725
2545
|
return rows.map((row) => ({
|
|
1726
2546
|
...mapSearchRowBase(row),
|
|
@@ -1816,6 +2636,21 @@ async function assertDocumentAccountWorkspace(db, accountId, workspaceId) {
|
|
|
1816
2636
|
}
|
|
1817
2637
|
return context;
|
|
1818
2638
|
}
|
|
2639
|
+
async function resolveEffectiveDocumentAccess(_db, input) {
|
|
2640
|
+
if (input.surface === "human") {
|
|
2641
|
+
return { viewerSubjectId: input.initiatingSubjectId };
|
|
2642
|
+
}
|
|
2643
|
+
return {
|
|
2644
|
+
agentOnly: true,
|
|
2645
|
+
viewerSubjectId: input.initiatingSubjectId,
|
|
2646
|
+
authorizedPersonalAttempt: input.agentAuthority ? {
|
|
2647
|
+
accountId: input.accountId,
|
|
2648
|
+
workspaceId: input.workspaceId,
|
|
2649
|
+
sessionId: input.agentAuthority.sessionId,
|
|
2650
|
+
attemptId: input.agentAuthority.attemptId
|
|
2651
|
+
} : void 0
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
1819
2654
|
function documentAccessConditions(workspaceId, access) {
|
|
1820
2655
|
const organization = eq(schema.documents.authorityKind, "organization");
|
|
1821
2656
|
const workspace = and(
|
|
@@ -1823,10 +2658,28 @@ function documentAccessConditions(workspaceId, access) {
|
|
|
1823
2658
|
eq(schema.documents.authorityWorkspaceId, workspaceId)
|
|
1824
2659
|
);
|
|
1825
2660
|
const viewer = cleanString(access?.viewerSubjectId ?? null);
|
|
2661
|
+
const personalAttempt = access?.authorizedPersonalAttempt;
|
|
2662
|
+
const authorizedPersonal = personalAttempt ? sql`${schema.documents.id} IN (
|
|
2663
|
+
SELECT resolve_session_attempt_personal_document_reads(
|
|
2664
|
+
${personalAttempt.accountId}::uuid,
|
|
2665
|
+
${personalAttempt.workspaceId}::uuid,
|
|
2666
|
+
${personalAttempt.sessionId}::uuid,
|
|
2667
|
+
${personalAttempt.attemptId}::uuid
|
|
2668
|
+
)
|
|
2669
|
+
)` : sql`false`;
|
|
1826
2670
|
const personal = viewer ? and(
|
|
1827
2671
|
eq(schema.documents.authorityKind, "personal"),
|
|
1828
|
-
eq(schema.documents.
|
|
1829
|
-
|
|
2672
|
+
eq(schema.documents.authoritySubjectId, viewer),
|
|
2673
|
+
access?.agentOnly ? or(
|
|
2674
|
+
and(
|
|
2675
|
+
isNull(schema.documents.authorityId),
|
|
2676
|
+
eq(schema.documents.authorityWorkspaceId, workspaceId)
|
|
2677
|
+
),
|
|
2678
|
+
and(isNotNull(schema.documents.authorityId), authorizedPersonal)
|
|
2679
|
+
) ?? authorizedPersonal : or(
|
|
2680
|
+
eq(schema.documents.authorityWorkspaceId, workspaceId),
|
|
2681
|
+
isNull(schema.documents.authorityWorkspaceId)
|
|
2682
|
+
) ?? eq(schema.documents.authorityWorkspaceId, workspaceId)
|
|
1830
2683
|
) : void 0;
|
|
1831
2684
|
const authority = viewer ? or(organization, workspace, personal) ?? organization : or(organization, workspace) ?? organization;
|
|
1832
2685
|
const viewerSql = viewer ? sql`${viewer}` : sql`NULL::text`;
|
|
@@ -1843,7 +2696,7 @@ function documentAccessConditions(workspaceId, access) {
|
|
|
1843
2696
|
}
|
|
1844
2697
|
function documentMatchesAccess(document, workspaceId, access) {
|
|
1845
2698
|
if (access?.agentOnly) {
|
|
1846
|
-
return document.agentAccess && canViewDocument(document, access.viewerSubjectId, workspaceId);
|
|
2699
|
+
return document.agentAccess && (document.authorityKind !== "personal" || document.authorityId === null) && canViewDocument(document, access.viewerSubjectId, workspaceId);
|
|
1847
2700
|
}
|
|
1848
2701
|
return canViewDocument(document, access?.viewerSubjectId, workspaceId);
|
|
1849
2702
|
}
|
|
@@ -1861,16 +2714,16 @@ function canViewDocument(document, viewerSubjectId, workspaceId) {
|
|
|
1861
2714
|
return document.authorityWorkspaceId === null && document.authoritySubjectId === null;
|
|
1862
2715
|
}
|
|
1863
2716
|
const normalizedWorkspaceId = cleanString(workspaceId ?? null);
|
|
1864
|
-
if (!normalizedWorkspaceId
|
|
2717
|
+
if (!normalizedWorkspaceId) {
|
|
1865
2718
|
return false;
|
|
1866
2719
|
}
|
|
1867
2720
|
if (document.authorityKind === "workspace") {
|
|
1868
|
-
return document.authoritySubjectId === null;
|
|
2721
|
+
return document.authorityWorkspaceId === normalizedWorkspaceId && document.authoritySubjectId === null;
|
|
1869
2722
|
}
|
|
1870
2723
|
if (document.authorityKind === "personal") {
|
|
1871
2724
|
const authoritySubjectId = canonicalDocumentAuthoritySubject(document.authoritySubjectId);
|
|
1872
2725
|
const viewer = canonicalDocumentAuthoritySubject(viewerSubjectId);
|
|
1873
|
-
return !!authoritySubjectId && authoritySubjectId === viewer;
|
|
2726
|
+
return !!authoritySubjectId && authoritySubjectId === viewer && (document.authorityWorkspaceId === null || document.authorityWorkspaceId === normalizedWorkspaceId);
|
|
1874
2727
|
}
|
|
1875
2728
|
return false;
|
|
1876
2729
|
}
|
|
@@ -1960,7 +2813,7 @@ function mergeDocumentSearchRows(rows, mode) {
|
|
|
1960
2813
|
matchType
|
|
1961
2814
|
};
|
|
1962
2815
|
}).sort(
|
|
1963
|
-
(left, right) => right.score - left.score || (right.vectorScore ?? 0) - (left.vectorScore ?? 0) || (right.keywordScore ?? 0) - (left.keywordScore ?? 0) || left.chunkIndex - right.chunkIndex
|
|
2816
|
+
(left, right) => right.score - left.score || (right.vectorScore ?? 0) - (left.vectorScore ?? 0) || (right.keywordScore ?? 0) - (left.keywordScore ?? 0) || left.chunkIndex - right.chunkIndex || (left.chunkId === right.chunkId ? 0 : left.chunkId < right.chunkId ? -1 : 1)
|
|
1964
2817
|
);
|
|
1965
2818
|
}
|
|
1966
2819
|
function combinedSearchScore(mode, vectorScore, keywordScore, matchType) {
|
|
@@ -2147,6 +3000,7 @@ function mapDocument(row) {
|
|
|
2147
3000
|
authorityKind: normalizeDocumentAuthorityKind(row.authorityKind),
|
|
2148
3001
|
authorityWorkspaceId: row.authorityWorkspaceId,
|
|
2149
3002
|
authoritySubjectId: row.authoritySubjectId,
|
|
3003
|
+
authorityId: row.authorityId,
|
|
2150
3004
|
visibility: normalizeDocumentVisibility(row.visibility),
|
|
2151
3005
|
createdBy: row.createdBy,
|
|
2152
3006
|
agentAccess: row.agentAccess,
|
|
@@ -2251,23 +3105,35 @@ export {
|
|
|
2251
3105
|
getDocument,
|
|
2252
3106
|
getDocumentBase,
|
|
2253
3107
|
getDocumentChunk,
|
|
3108
|
+
getDocumentDefaultCollectionBackfillAudit,
|
|
2254
3109
|
getDocumentForIndexing,
|
|
2255
3110
|
getDocumentInventory,
|
|
3111
|
+
getDocumentOriginalFile,
|
|
2256
3112
|
getEffectiveKnowledgeRecord,
|
|
2257
3113
|
heuristicCuration,
|
|
2258
3114
|
indexDocumentNow,
|
|
3115
|
+
listAccessibleDocuments,
|
|
3116
|
+
listDocumentAuthorityReclassifications,
|
|
2259
3117
|
listDocumentBases,
|
|
2260
3118
|
listDocumentBasesEnsuringDefault,
|
|
3119
|
+
listDocumentDefaultCollectionBackfillRuns,
|
|
2261
3120
|
listDocuments,
|
|
2262
3121
|
listEffectiveIndexedDocuments,
|
|
3122
|
+
listOrganizationDocumentAuthorityReclassifications,
|
|
2263
3123
|
moveDocumentToBase,
|
|
2264
3124
|
parseCurationOutcome,
|
|
2265
3125
|
parseDocumentBytes,
|
|
2266
3126
|
projectKnowledgeRecord,
|
|
2267
3127
|
queueDocumentForReindex,
|
|
3128
|
+
reclassifyDocumentAuthority,
|
|
2268
3129
|
resolveDocumentAuthority,
|
|
3130
|
+
resolveEffectiveDocumentAccess,
|
|
3131
|
+
runDocumentDefaultCollectionBackfill,
|
|
2269
3132
|
searchDocuments,
|
|
2270
3133
|
searchEffectiveDocuments,
|
|
2271
|
-
searchEffectiveKnowledge
|
|
3134
|
+
searchEffectiveKnowledge,
|
|
3135
|
+
selectDocumentSearchCandidateWindow,
|
|
3136
|
+
selectKnowledgeBrowseRecords,
|
|
3137
|
+
selectKnowledgeSearchResults
|
|
2272
3138
|
};
|
|
2273
3139
|
//# sourceMappingURL=index.js.map
|