@@ -328,6 +328,26 @@ export function partitionSyncReconciliation(
328328 return { resurrectIds, softDeleteIds : liveMissingIds , hardDeleteIds : tombstonedStillMissingIds }
329329}
330330
331+ /**
332+ * Re-filters the three reconciliation ID lists against a fresh ownership
333+ * snapshot taken under the connector's `FOR UPDATE` lock, dropping any
334+ * document a concurrent "delete connector, keep documents" request already
335+ * detached (its `connectorId` no longer matches) since the lists were first
336+ * computed.
337+ */
338+ export function filterStillOwnedReconciliationIds (
339+ resurrectIds : string [ ] ,
340+ softDeleteIds : string [ ] ,
341+ hardDeleteIds : string [ ] ,
342+ stillOwnedIds : Set < string >
343+ ) : { resurrectIds : string [ ] ; softDeleteIds : string [ ] ; hardDeleteIds : string [ ] } {
344+ return {
345+ resurrectIds : resurrectIds . filter ( ( id ) => stillOwnedIds . has ( id ) ) ,
346+ softDeleteIds : softDeleteIds . filter ( ( id ) => stillOwnedIds . has ( id ) ) ,
347+ hardDeleteIds : hardDeleteIds . filter ( ( id ) => stillOwnedIds . has ( id ) ) ,
348+ }
349+ }
350+
331351/**
332352 * Resolves tag values from connector metadata using the connector's mapTags function.
333353 * Translates semantic keys returned by mapTags to actual DB slots using the
@@ -516,6 +536,10 @@ export async function executeSync(
516536 let hasMore = true
517537 const syncContext : Record < string , unknown > = { syncRunId : generateId ( ) }
518538
539+ // Shared cutoff for both the tombstone-retry bound below and the stuck-document
540+ // retry near the end of this sync — same RETRY_WINDOW_DAYS window, one computation.
541+ const retryCutoff = new Date ( Date . now ( ) - RETRY_WINDOW_DAYS * 24 * 60 * 60 * 1000 )
542+
519543 /**
520544 * Bounded to the same retry window as the stuck-document retry below: a
521545 * document whose refresh keeps failing every sync (e.g. permanently
@@ -543,7 +567,7 @@ export async function executeSync(
543567 eq ( document . connectorId , connectorId ) ,
544568 isNull ( document . archivedAt ) ,
545569 isNotNull ( document . deletedAt ) ,
546- gt ( document . deletedAt , new Date ( Date . now ( ) - RETRY_WINDOW_DAYS * 24 * 60 * 60 * 1000 ) )
570+ gt ( document . deletedAt , retryCutoff )
547571 )
548572 )
549573 . limit ( 1 )
@@ -991,9 +1015,15 @@ export async function executeSync(
9911015 ) . map ( ( d ) => d . id )
9921016 )
9931017
994- safeResurrectIds = resurrectIds . filter ( ( id ) => stillOwned . has ( id ) )
995- safeSoftDeleteIds = gatedSoftDeleteIds . filter ( ( id ) => stillOwned . has ( id ) )
996- safeHardDeleteIds = gatedHardDeleteIds . filter ( ( id ) => stillOwned . has ( id ) )
1018+ const stillOwnedResult = filterStillOwnedReconciliationIds (
1019+ resurrectIds ,
1020+ gatedSoftDeleteIds ,
1021+ gatedHardDeleteIds ,
1022+ stillOwned
1023+ )
1024+ safeResurrectIds = stillOwnedResult . resurrectIds
1025+ safeSoftDeleteIds = stillOwnedResult . softDeleteIds
1026+ safeHardDeleteIds = stillOwnedResult . hardDeleteIds
9971027
9981028 /**
9991029 * A document reappearing at the source is trustworthy evidence on its
@@ -1055,7 +1085,6 @@ export async function executeSync(
10551085 // abandoned (e.g. the Trigger.dev task process exited before processing completed).
10561086 // Documents uploaded more than RETRY_WINDOW_DAYS ago are not retried.
10571087 const staleProcessingCutoff = new Date ( Date . now ( ) - STALE_PROCESSING_MINUTES * 60 * 1000 )
1058- const retryCutoff = new Date ( Date . now ( ) - RETRY_WINDOW_DAYS * 24 * 60 * 60 * 1000 )
10591088 const stuckDocs = await db
10601089 . select ( {
10611090 id : document . id ,
0 commit comments