Skip to content

Commit 4ceff36

Browse files
committed
fix(knowledge): resurrect atomically on content update, sweep tombstoned docs on connector teardown
Two real gaps found by review, both stemming from the same root cause: other code paths assumed deletedAt IS NULL means 'the only real rows' and were never updated for the new tombstone semantics. - updateDocument's guard required deletedAt IS NULL, so a tombstoned document reappearing with CHANGED content failed its content update (rejected by the guard) while the separate resurrect step still cleared deletedAt regardless — the document became active again but kept serving stale pre-tombstone content. Fixed by clearing deletedAt as part of the same update statement and dropping the guard, so content and resurrection land atomically. - Both connector-teardown cleanup paths (the ConnectorDeletedException handler in sync-engine.ts, and the connector DELETE API route) only swept documents with deletedAt IS NULL, so pending-removal documents escaped cleanup entirely and were orphaned once their connector was gone. Fixed by including tombstoned docs in both sweeps — there's no future sync left to confirm or resurrect them once the connector itself is deleted.
1 parent 4e32f47 commit 4ceff36

2 files changed

Lines changed: 12 additions & 21 deletions

File tree

apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,16 +328,12 @@ export const DELETE = withRouteHandler(async (request: NextRequest, { params }:
328328
const { deletedDocs, docCount } = await db.transaction(async (tx) => {
329329
await tx.execute(sql`SELECT 1 FROM knowledge_connector WHERE id = ${connectorId} FOR UPDATE`)
330330

331+
// Includes pending-removal (tombstoned) docs — the connector is being
332+
// deleted, so there's no future sync left to confirm or resurrect them.
331333
const docs = await tx
332334
.select({ id: document.id, fileUrl: document.fileUrl })
333335
.from(document)
334-
.where(
335-
and(
336-
eq(document.connectorId, connectorId),
337-
isNull(document.archivedAt),
338-
isNull(document.deletedAt)
339-
)
340-
)
336+
.where(and(eq(document.connectorId, connectorId), isNull(document.archivedAt)))
341337

342338
if (deleteDocuments) {
343339
const documentIds = docs.map((doc) => doc.id)

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,16 +1002,12 @@ export async function executeSync(
10021002
logger.info('Connector deleted during sync, cleaning up', { connectorId })
10031003

10041004
try {
1005+
// Includes pending-removal (tombstoned) docs — the connector is gone, so
1006+
// there's no future sync left to confirm or resurrect them.
10051007
const connectorDocs = await db
10061008
.select({ id: document.id })
10071009
.from(document)
1008-
.where(
1009-
and(
1010-
eq(document.connectorId, connectorId),
1011-
isNull(document.archivedAt),
1012-
isNull(document.deletedAt)
1013-
)
1014-
)
1010+
.where(and(eq(document.connectorId, connectorId), isNull(document.archivedAt)))
10151011

10161012
await hardDeleteDocuments(
10171013
connectorDocs.map((doc) => doc.id),
@@ -1341,14 +1337,13 @@ async function updateDocument(
13411337
...tagValues,
13421338
processingStatus: 'pending',
13431339
uploadedAt: new Date(),
1340+
// A tombstoned document reappearing with changed content is resurrected
1341+
// in the same write as its content update — otherwise reconciliation's
1342+
// separate resurrect step would clear deletedAt while this update, gated
1343+
// on deletedAt IS NULL, rejects the row and leaves stale content active.
1344+
deletedAt: null,
13441345
})
1345-
.where(
1346-
and(
1347-
eq(document.id, existingDocId),
1348-
isNull(document.archivedAt),
1349-
isNull(document.deletedAt)
1350-
)
1351-
)
1346+
.where(and(eq(document.id, existingDocId), isNull(document.archivedAt)))
13521347
.returning({ id: document.id })
13531348
.then((rows) => {
13541349
if (rows.length === 0) {

0 commit comments

Comments
 (0)