@@ -364,9 +364,11 @@ export async function completeSyncLog(
364364 * proves the lock is still this run's. `status` is kept alongside as defence in
365365 * depth and to cover a user pausing the connector mid-run.
366366 *
367- * Guards both terminal paths. The failure path needs it as much as the success
368- * path: a reclaimed run's failure would double-increment a counter the sweep
369- * already advanced and overwrite its backoff with a shorter one.
367+ * Guards every write a run makes to its own connector row: both terminal paths
368+ * and the mid-run heartbeat. The failure path needs it as much as the success
369+ * path — a reclaimed run's failure would double-increment a counter the sweep
370+ * already advanced and overwrite its backoff with a shorter one — and reusing it
371+ * for the heartbeat is what turns a beat into an ownership probe.
370372 */
371373export function stillHoldsSyncLock ( connectorId : string , syncLockToken : string ) {
372374 return and (
@@ -396,7 +398,7 @@ export function buildSyncLockAcquisition(syncLogId: string, now: Date) {
396398/**
397399 * Whether a running sync is due to refresh its lock.
398400 *
399- * Time-based rather than batch-count-based: batches vary hugely in cost, so a
401+ * Time-based rather than batch-count-based: batches vary hugely in cost, so an
400402 * every-N-batches beat would fire constantly on small documents and barely at
401403 * all on large ones — exactly the runs that need it.
402404 */
@@ -457,9 +459,10 @@ export async function writeTerminalConnectorState(
457459}
458460
459461/**
460- * Reported when a run's terminal write matched no rows because the run no longer
461- * held its lock. Its document writes still landed; only its connector-level
462- * bookkeeping was discarded, in favour of whoever reclaimed the row.
462+ * Reported when a run loses its connector's lock mid-flight — either because a
463+ * heartbeat found the lock reclaimed, or because its terminal write matched no
464+ * rows. Its document writes still landed; only its connector-level bookkeeping
465+ * was discarded, in favour of whoever reclaimed the row.
463466 */
464467export const SUPERSEDED_SYNC_ERROR = 'sync_superseded'
465468
@@ -468,11 +471,7 @@ export const SUPERSEDED_SYNC_ERROR = 'sync_superseded'
468471 * report a discarded run as a clean sync — the same reason a lock-contended run
469472 * returns `sync_in_progress` rather than an empty success.
470473 */
471- export function applySupersededOutcome (
472- result : SyncResult ,
473- terminalWriteLanded : boolean
474- ) : SyncResult {
475- if ( terminalWriteLanded ) return result
474+ export function markSyncSuperseded ( result : SyncResult ) : SyncResult {
476475 return { ...result , error : SUPERSEDED_SYNC_ERROR }
477476}
478477
@@ -595,8 +594,8 @@ export function classifySuspectListing(
595594 * immediately. A genuinely emptied source keeps reconciling: its second sync
596595 * corroborates the first and tombstones everything, and a later sync — once the
597596 * tombstoned set is again absent — completes the two-strike purge, subject to
598- * {@link capReconciliationDeletions}, which holds a pass whose deletion count
599- * exceeds the per-sync blast-radius cap.
597+ * {@link capReconciliationDeletions}, which withholds any generation whose
598+ * deletion count exceeds the per-sync blast-radius cap.
600599 *
601600 * A forced `fullSync` overrides the guard, matching its existing meaning
602601 * elsewhere here — an explicit human request to reconcile against this listing
@@ -2033,7 +2032,7 @@ export async function executeSync(
20332032 syncLogId,
20342033 ...result ,
20352034 } )
2036- return applySupersededOutcome ( result , false )
2035+ return markSyncSuperseded ( result )
20372036 }
20382037
20392038 logger . info ( 'Sync completed' , { connectorId, ...result } )
@@ -2050,7 +2049,7 @@ export async function executeSync(
20502049 syncLogId,
20512050 ...result ,
20522051 } )
2053- return applySupersededOutcome ( result , false )
2052+ return markSyncSuperseded ( result )
20542053 }
20552054
20562055 if ( error instanceof ConnectorDeletedException ) {
@@ -2108,7 +2107,7 @@ export async function executeSync(
21082107 )
21092108
21102109 /**
2111- * Deliberately does NOT get {@link applySupersededOutcome }. `result.error`
2110+ * Deliberately does NOT get {@link markSyncSuperseded }. `result.error`
21122111 * is set to the real failure cause below and the task wrapper already
21132112 * reports this run as unsuccessful, so overwriting it with
21142113 * `sync_superseded` would destroy the diagnostic without changing the
0 commit comments