@@ -11,7 +11,7 @@ import {
1111 knowledgeDocumentObservation ,
1212} from '@sim/db/schema'
1313import { createLogger } from '@sim/logger'
14- import { getErrorMessage , toError } from '@sim/utils/errors'
14+ import { getErrorMessage , getTransientDatabaseFailure , toError } from '@sim/utils/errors'
1515import { generateId } from '@sim/utils/id'
1616import { randomInt } from '@sim/utils/random'
1717import { and , asc , eq , gt , inArray , isNull , lte , notExists , sql } from 'drizzle-orm'
@@ -65,6 +65,10 @@ import {
6565} from '@/lib/knowledge/connectors/member-observations'
6666import { inviteWorkspaceMembersToCredentialGroup } from '@/lib/knowledge/connectors/member-provisioning'
6767import { runConnectorContentPass } from '@/lib/knowledge/connectors/sync-content-pass'
68+ import {
69+ countFailedRunStreak ,
70+ databaseRetryDelayMs ,
71+ } from '@/lib/knowledge/connectors/sync-database-retry'
6872import {
6973 deferConnectorSync ,
7074 getConnectorSyncDeferral ,
@@ -327,6 +331,73 @@ export function buildMemberSyncFailureUpdate(
327331 }
328332}
329333
334+ /**
335+ * The connector row written after the database, not the source, failed a members-mode run. The
336+ * members-mode counterpart of `buildSyncDatabaseRetryUpdate`: the breaker keeps only the source
337+ * failures already counted, while the retry climbs the failure ladder by the failed-run streak.
338+ */
339+ export function buildMemberSyncDatabaseRetryUpdate (
340+ now : Date ,
341+ previousFailures : number | null | undefined ,
342+ errorMessage : string ,
343+ failedRunStreak : number
344+ ) {
345+ return {
346+ memberSyncStatus : 'error' as const ,
347+ lastMemberSyncError : errorMessage ,
348+ nextMemberSyncAt : new Date (
349+ now . getTime ( ) + databaseRetryDelayMs ( failedRunStreak , previousFailures )
350+ ) ,
351+ memberSyncConsecutiveFailures : previousFailures ?? 0 ,
352+ memberSyncLockToken : null ,
353+ memberSyncLockLeaseAt : null ,
354+ updatedAt : now ,
355+ }
356+ }
357+
358+ /**
359+ * The connector row a failed members-mode run writes. A deterministic capacity rejection waits for
360+ * an operator, a transient database failure retries without touching the breaker, and anything
361+ * else climbs the ladder toward auto-disable.
362+ */
363+ export async function resolveMemberSyncFailureUpdate (
364+ error : unknown ,
365+ failure : {
366+ connectorId : string
367+ runId : string
368+ previousFailures : number
369+ errorMessage : string
370+ retryAfterMs ?: number
371+ }
372+ ) {
373+ const now = new Date ( )
374+ if ( error instanceof ConnectorSyncCapacityError ) {
375+ return {
376+ memberSyncStatus : 'error' as const ,
377+ lastMemberSyncError : failure . errorMessage ,
378+ nextMemberSyncAt : null ,
379+ memberSyncConsecutiveFailures : failure . previousFailures ,
380+ memberSyncLockToken : null ,
381+ memberSyncLockLeaseAt : null ,
382+ updatedAt : now ,
383+ }
384+ }
385+ if ( getTransientDatabaseFailure ( error ) ) {
386+ return buildMemberSyncDatabaseRetryUpdate (
387+ now ,
388+ failure . previousFailures ,
389+ failure . errorMessage ,
390+ await countFailedRunStreak ( 'member' , failure . connectorId , failure . runId )
391+ )
392+ }
393+ return buildMemberSyncFailureUpdate (
394+ now ,
395+ failure . previousFailures ,
396+ failure . errorMessage ,
397+ failure . retryAfterMs
398+ )
399+ }
400+
330401/**
331402 * When a member who completed is next due: exactly one interval on, with no
332403 * jitter, so they are due whenever the connector's own (jittered) run lands.
@@ -2422,23 +2493,13 @@ export async function executeMemberSync(
24222493 logger . error ( 'Member sync failed' , { connectorId, runId, error : errorMessage , diagnostic } )
24232494 try {
24242495 await failMemberSyncLog ( runId , result , errorMessage )
2425- const failureUpdate =
2426- error instanceof ConnectorSyncCapacityError
2427- ? {
2428- memberSyncStatus : 'error' as const ,
2429- lastMemberSyncError : errorMessage ,
2430- nextMemberSyncAt : null ,
2431- memberSyncConsecutiveFailures : connector . memberSyncConsecutiveFailures ,
2432- memberSyncLockToken : null ,
2433- memberSyncLockLeaseAt : null ,
2434- updatedAt : new Date ( ) ,
2435- }
2436- : buildMemberSyncFailureUpdate (
2437- new Date ( ) ,
2438- connector . memberSyncConsecutiveFailures ,
2439- errorMessage ,
2440- retryAfterMs
2441- )
2496+ const failureUpdate = await resolveMemberSyncFailureUpdate ( error , {
2497+ connectorId,
2498+ runId,
2499+ previousFailures : connector . memberSyncConsecutiveFailures ,
2500+ errorMessage,
2501+ retryAfterMs,
2502+ } )
24422503 const written = await db
24432504 . update ( knowledgeConnector )
24442505 . set ( failureUpdate )
0 commit comments