Skip to content

Commit 180cb12

Browse files
committed
fix(knowledge): grant access after dispatch, include skipped rows, and keep the grant best-effort
1 parent ebeafe5 commit 180cb12

4 files changed

Lines changed: 46 additions & 20 deletions

File tree

‎apps/sim/lib/knowledge/connectors/member-sync-engine.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ import {
5959
SyncLockLostException,
6060
stillHoldsMemberSyncLock,
6161
} from '@/lib/knowledge/connectors/sync-lock'
62-
import type { KnowledgeBaseOwner } from '@/lib/knowledge/connectors/sync-persistence'
62+
import type {
63+
KnowledgeBaseOwner,
64+
PersistedDocument,
65+
} from '@/lib/knowledge/connectors/sync-persistence'
6366
import {
6467
addSourcePagePayloadBytes,
6568
ConnectorDeletedException,
@@ -69,7 +72,6 @@ import {
6972
classifySuspectListing,
7073
createSyncRunState,
7174
loadOwnedCorpus,
72-
type PersistedDocument,
7375
processDocOps,
7476
RETRY_WINDOW_DAYS,
7577
runChangeFeedPass,

‎apps/sim/lib/knowledge/connectors/sync-engine.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ describe('persistSkippedDocuments', () => {
789789
'workspace',
790790
lease
791791
)
792-
).resolves.toBe(1)
792+
).resolves.toHaveLength(1)
793793

794794
expect(dbChainMockFns.values).toHaveBeenCalledWith([
795795
expect.objectContaining({
@@ -835,7 +835,7 @@ describe('persistSkippedDocuments', () => {
835835
'workspace',
836836
lease
837837
)
838-
).resolves.toBe(1)
838+
).resolves.toHaveLength(1)
839839

840840
expect(dbChainMockFns.set).toHaveBeenCalledWith(
841841
expect.objectContaining({

‎apps/sim/lib/knowledge/connectors/sync-persistence.ts‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ function buildSkippedDocumentRow(
247247
*
248248
* Returns the number of rows recorded.
249249
*/
250+
/** A document a sync wrote, by the id the source knows it by and the id the row has. */
251+
export interface PersistedDocument {
252+
externalId: string
253+
documentId: string
254+
}
255+
250256
export async function persistSkippedDocuments(
251257
knowledgeBaseId: string,
252258
connectorId: string,
@@ -259,9 +265,9 @@ export async function persistSkippedDocuments(
259265
sourceConfig: Record<string, unknown> | undefined,
260266
access: SyncDocumentAccess,
261267
lease: SyncWriteLease
262-
): Promise<number> {
268+
): Promise<PersistedDocument[]> {
263269
if (skipOps.length === 0) {
264-
return 0
270+
return []
265271
}
266272
const inserts = skipOps
267273
.filter((op) => !op.existingId)
@@ -275,6 +281,12 @@ export async function persistSkippedDocuments(
275281
access
276282
)
277283
)
284+
const persisted: PersistedDocument[] = [
285+
...inserts.map((row) => ({ externalId: row.externalId, documentId: row.id })),
286+
...skipOps
287+
.filter((op): op is typeof op & { existingId: string } => Boolean(op.existingId))
288+
.map((op) => ({ externalId: op.extDoc.externalId, documentId: op.existingId })),
289+
]
278290
const replacements = skipOps.filter((op): op is typeof op & { existingId: string } =>
279291
Boolean(op.existingId)
280292
)
@@ -363,7 +375,7 @@ export async function persistSkippedDocuments(
363375
}
364376
}
365377

366-
return skipOps.length
378+
return persisted
367379
}
368380

369381
/**

‎apps/sim/lib/knowledge/connectors/sync-primitives.ts‎

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { SyncLockLostException, type SyncRunLease } from '@/lib/knowledge/connec
1616
import {
1717
addDocument,
1818
type KnowledgeBaseOwner,
19+
type PersistedDocument,
1920
persistSkippedDocuments,
2021
persistSkippedRetryHashes,
2122
type SyncDocumentAccess,
@@ -1497,9 +1498,13 @@ export interface ProcessDocOpsInput {
14971498
hydration: DocOpHydration
14981499
lease: Pick<SyncRunLease, 'beatIfDue' | 'beatLive' | 'stillHeld'>
14991500
/**
1500-
* Runs after each batch's rows are written, with the documents that landed.
1501-
* A members-mode run grants access here, batch by batch, so a long first
1502-
* crawl becomes searchable as it goes rather than all at once at the end.
1501+
* Runs after each batch is written and dispatched, with every row that
1502+
* landed: hydrated documents and skipped ones alike. A members-mode run
1503+
* grants access here, batch by batch, so a long first crawl becomes
1504+
* searchable as it goes rather than all at once at the end. Best effort: a
1505+
* failure here is logged and the run continues, because the listing's own
1506+
* pass at the end of the run writes the same grants authoritatively; only a
1507+
* lost lease ends the run.
15031508
*/
15041509
onBatchPersisted?: (persisted: readonly PersistedDocument[]) => Promise<void>
15051510
/** Who may read the documents this pass writes. */
@@ -1513,12 +1518,6 @@ export interface ProcessDocOpsInput {
15131518
* run so the connector backs off, and a lost lease, which ends it so no
15141519
* further write lands beside the replacement run's.
15151520
*/
1516-
/** A document a batch wrote, by the id the source knows it by and the id the row has. */
1517-
export interface PersistedDocument {
1518-
externalId: string
1519-
documentId: string
1520-
}
1521-
15221521
export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
15231522
const {
15241523
connectorId,
@@ -1689,6 +1688,7 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
16891688
}
16901689
}
16911690

1691+
const skippedPersisted: PersistedDocument[] = []
16921692
if (skipOps.length > 0) {
16931693
try {
16941694
const recorded = await persistSkippedDocuments(
@@ -1700,7 +1700,8 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
17001700
documentAccess,
17011701
input.lease
17021702
)
1703-
result.docsSkipped += recorded
1703+
result.docsSkipped += recorded.length
1704+
skippedPersisted.push(...recorded)
17041705
} catch (error) {
17051706
if (error instanceof SyncLockLostException) throw error
17061707
/**
@@ -1756,7 +1757,7 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
17561757
if (leaseLost) throw leaseLost.reason
17571758

17581759
const batchDocs: DocumentData[] = []
1759-
const persisted: PersistedDocument[] = []
1760+
const persisted: PersistedDocument[] = [...skippedPersisted]
17601761
for (let j = 0; j < settled.length; j++) {
17611762
const outcome = settled[j]
17621763
if (outcome.status === 'fulfilled') {
@@ -1778,8 +1779,6 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
17781779
}
17791780
}
17801781

1781-
if (persisted.length > 0) await input.onBatchPersisted?.(persisted)
1782-
17831782
if (batchDocs.length > 0) {
17841783
result.processingDispatch.requested += batchDocs.length
17851784
try {
@@ -1803,6 +1802,19 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise<void> {
18031802
})
18041803
}
18051804
}
1805+
1806+
if (persisted.length > 0 && input.onBatchPersisted) {
1807+
try {
1808+
await input.onBatchPersisted(persisted)
1809+
} catch (error) {
1810+
if (error instanceof SyncLockLostException) throw error
1811+
logger.warn('Failed to grant access for a persisted batch — the run end will retry', {
1812+
connectorId,
1813+
count: persisted.length,
1814+
error: toError(error).message,
1815+
})
1816+
}
1817+
}
18061818
}
18071819
}
18081820

0 commit comments

Comments
 (0)