Skip to content

Commit f289f68

Browse files
committed
fix(knowledge): keep the corpus size on a failed completion count, unschedule only connectors left without an auth source, and give fixture connectors a credential
1 parent 81d1ae9 commit f289f68

20 files changed

Lines changed: 124 additions & 46 deletions

‎apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connector-recovery.tsx‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
44
import { Chip } from '@sim/emcn'
55
import type { ConnectorData } from '@/lib/api/contracts/knowledge/connectors'
66
import { type ResourceScope, resourceScopeFields } from '@/lib/core/resource-scope'
7+
import { CREDENTIAL_REMOVED_SYNC_ERROR } from '@/lib/knowledge/connectors/sync-limits'
78
import { getCanonicalScopesForProvider, getProviderIdFromServiceId } from '@/lib/oauth'
89
import { getMissingRequiredScopes } from '@/lib/oauth/utils'
910
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
@@ -79,6 +80,10 @@ export function ConnectorRecovery({
7980
}
8081

8182
const docsUrl = isSearchIndex ? connectorDef?.searchDocsUrl : undefined
83+
const credentialRemoved = connector.lastSyncError === CREDENTIAL_REMOVED_SYNC_ERROR
84+
const pausedTitle = credentialRemoved
85+
? 'Reconnect to resume syncing'
86+
: 'Sync paused after repeated failures'
8287

8388
return (
8489
<>
@@ -91,16 +96,16 @@ export function ConnectorRecovery({
9196
}
9297
/>
9398
)}
94-
{connector.status === 'disabled' ? (
99+
{connector.status === 'disabled' || credentialRemoved ? (
95100
<SettingsResourceRow
96101
title={
97102
!canEdit
98-
? 'Sync paused after repeated failures'
103+
? pausedTitle
99104
: requiresAccountSettings
100105
? 'Update the source account, then resume syncing'
101106
: serviceId
102107
? 'Reconnect to resume syncing'
103-
: 'Sync paused after repeated failures'
108+
: pausedTitle
104109
}
105110
trailing={
106111
canEdit && requiresAccountSettings && onEdit ? (

‎apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
88
import type { SyncLogData } from '@/lib/api/contracts/knowledge/connectors'
99
import {
1010
CONNECTOR_SYNC_STALE_LOCK_TTL_MS,
11+
CREDENTIAL_REMOVED_SYNC_ERROR,
1112
MEMBER_SYNC_STALE_LOCK_TTL_MS,
1213
} from '@/lib/knowledge/connectors/sync-limits'
1314

@@ -480,6 +481,21 @@ describe('Connector credential reauthorization', () => {
480481
expect(container.textContent).toContain('No connected sources yet.')
481482
})
482483

484+
it('offers reconnect for a connector whose credential was removed', () => {
485+
oauthCredentialsState.current = [
486+
{ id: 'credential-1', name: 'Workspace Slack', provider: 'slack-custom' },
487+
]
488+
const container = renderSection(
489+
makeConnector({
490+
status: 'error',
491+
credentialId: null,
492+
lastSyncError: CREDENTIAL_REMOVED_SYNC_ERROR,
493+
})
494+
)
495+
expect(container.textContent).toContain('Reconnect to resume syncing')
496+
expect(findButton(container, 'Reconnect')).not.toBeDisabled()
497+
})
498+
483499
it('reauthorizes with the resolved credential provider and identity', () => {
484500
oauthCredentialsState.current = [
485501
{ id: 'credential-1', name: 'Workspace Slack', provider: 'slack-custom' },

‎apps/sim/background/drain-governed-subject.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ vi.mock('@/enrichments/run', () => ({
4949
}))
5050
vi.mock('@/lib/billing/core/billing-attribution', () => ({
5151
assertBillingAttributionSnapshot: (snapshot: unknown) => snapshot,
52-
checkAttributedUsageLimits: mocks.checkAttributedUsageLimits,
5352
toBillingContext: () => ({}),
5453
}))
54+
vi.mock('@/lib/billing/core/usage-gate-cache', () => ({
55+
checkExecutionUsageLimits: mocks.checkAttributedUsageLimits,
56+
}))
5557
vi.mock('@/lib/table/rows/secret-provenance', () => ({
5658
createExactEmptyTableRowSecretProvenance: () => ({ complete: true, columns: {} }),
5759
createTableRowSecretProvenanceFromRegistry: () => ({ complete: true, columns: {} }),

‎apps/sim/background/enrichment-capability-subject.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ vi.mock('@/enrichments/run', () => ({
5858
}))
5959
vi.mock('@/lib/billing/core/billing-attribution', () => ({
6060
assertBillingAttributionSnapshot: vi.fn((value) => value),
61-
checkAttributedUsageLimits: mocks.checkAttributedUsageLimits,
6261
toBillingContext: vi.fn(() => ({})),
6362
}))
63+
vi.mock('@/lib/billing/core/usage-gate-cache', () => ({
64+
checkExecutionUsageLimits: mocks.checkAttributedUsageLimits,
65+
}))
6466
vi.mock('@/lib/table/rows/secret-provenance', () => ({
6567
createExactEmptyTableRowSecretProvenance: vi.fn(() => undefined),
6668
createTableRowSecretProvenanceFromRegistry: vi.fn(() => undefined),

‎apps/sim/background/knowledge-connector-sync.test.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
import { AbortTaskRunError } from '@trigger.dev/sdk'
66
import { beforeEach, describe, expect, it, vi } from 'vitest'
77

8-
const { mockAssertConnectorSyncPayload, mockExecuteSync, mockTask } = vi.hoisted(() => ({
8+
const { mockAssertConnectorSyncPayload, mockExecuteSync, mockTask, mockWarn } = vi.hoisted(() => ({
9+
mockWarn: vi.fn(),
910
mockAssertConnectorSyncPayload: vi.fn(),
1011
mockExecuteSync: vi.fn(),
1112
mockTask: vi.fn((config) => config),
1213
}))
1314

15+
vi.mock('@sim/logger', () => ({
16+
createLogger: () => ({ info: vi.fn(), warn: mockWarn, error: vi.fn(), debug: vi.fn() }),
17+
}))
1418
vi.mock('@trigger.dev/sdk', () => ({
1519
task: mockTask,
1620
AbortTaskRunError: class AbortTaskRunError extends Error {},
@@ -142,6 +146,9 @@ describe('knowledge connector sync worker', () => {
142146
docsFailed: 1,
143147
processingDispatch: { failed: 1 },
144148
})
149+
expect(mockWarn).toHaveBeenCalledWith(
150+
expect.stringContaining('1 source failures, 1 dispatch failures')
151+
)
145152
})
146153

147154
it('completes a durably scheduled capacity wait while preserving existing source failures', async () => {
@@ -270,6 +277,7 @@ describe('knowledge connector sync worker', () => {
270277
outcome: 'partial',
271278
listingIncomplete: true,
272279
})
280+
expect(mockWarn).not.toHaveBeenCalled()
273281
})
274282

275283
it('classifies a persisted connector error as a failed task', () => {

‎apps/sim/background/workflow-group-governed-subject.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ vi.mock('@/executor/utils/resolved-secret-trace-registry', () => ({
9393
}))
9494
vi.mock('@/lib/billing/core/billing-attribution', () => ({
9595
assertBillingAttributionSnapshot: (snapshot: unknown) => snapshot,
96-
checkAttributedUsageLimits: async () => ({ isExceeded: false }),
9796
toBillingContext: () => ({}),
9897
}))
98+
vi.mock('@/lib/billing/core/usage-gate-cache', () => ({
99+
checkExecutionUsageLimits: async () => ({ isExceeded: false }),
100+
}))
99101
/** Real pacing would sleep jittered backoff against the global db mock. */
100102
vi.mock('@/lib/core/rate-limiter/rate-limiter', () => ({
101103
RateLimiter: class {

‎apps/sim/lib/copilot/request/tools/workflow-context.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ const { checkAttributedUsageLimitsMock, reserveExecutionSlotMock, resolveBilling
1313
}))
1414

1515
vi.mock('@/lib/billing/core/billing-attribution', () => ({
16-
checkAttributedUsageLimits: checkAttributedUsageLimitsMock,
1716
resolveBillingAttribution: resolveBillingAttributionMock,
1817
}))
18+
vi.mock('@/lib/billing/core/usage-gate-cache', () => ({
19+
checkExecutionUsageLimits: checkAttributedUsageLimitsMock,
20+
}))
1921

2022
vi.mock('@/lib/billing/calculations/usage-reservation', () => ({
2123
reserveExecutionSlot: reserveExecutionSlotMock,

‎apps/sim/lib/credentials/deletion.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ describe('clearCredentialRefs', () => {
215215
`CASE WHEN "knowledge_connector"."status" IN ('paused', 'disabled')`
216216
)
217217
expect(statement).toContain('"access_mode" in ($')
218+
expect(statement).toContain('"encrypted_api_key" is null')
218219
expect(updates[0].params).toEqual(
219220
expect.arrayContaining([
220221
'Credential removed. Reconnect the connector to resume syncing.',

‎apps/sim/lib/credentials/deletion.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
22
import { db } from '@sim/db'
33
import * as schema from '@sim/db/schema'
44
import { createLogger } from '@sim/logger'
5-
import { and, eq, inArray, notExists, or, sql } from 'drizzle-orm'
5+
import { and, eq, inArray, isNull, notExists, or, sql } from 'drizzle-orm'
66
import type { AnyPgColumn, PgTable } from 'drizzle-orm/pg-core'
77
import type { NextRequest } from 'next/server'
88
import {
@@ -342,8 +342,8 @@ async function readWorkspaceCredentialRefs(
342342
/**
343343
* A content-engine connector whose credential is gone cannot sync until it is reconnected, so
344344
* it leaves the due sweep with the reconnect error; paused and disabled connectors keep their
345-
* status. A members-mode connector only loses its optional dedicated content credential: its
346-
* member crawls keep running, so it merely drops the reference.
345+
* status. A connector that still holds an API key, or a members-mode connector that only loses
346+
* its optional dedicated content credential, keeps running and merely drops the reference.
347347
*/
348348
async function clearInKnowledgeConnectors(credentialId: string): Promise<void> {
349349
const now = new Date()
@@ -357,7 +357,8 @@ async function clearInKnowledgeConnectors(credentialId: string): Promise<void> {
357357
.where(
358358
and(
359359
eq(schema.knowledgeConnector.credentialId, credentialId),
360-
inArray(schema.knowledgeConnector.accessMode, [...CONTENT_ENGINE_ACCESS_MODES])
360+
inArray(schema.knowledgeConnector.accessMode, [...CONTENT_ENGINE_ACCESS_MODES]),
361+
isNull(schema.knowledgeConnector.encryptedApiKey)
361362
)
362363
)
363364
await db

‎apps/sim/lib/execution/preprocessing.webhook-correlation.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ vi.mock('@/lib/billing/calculations/usage-monitor', () => ({
1414
}))
1515
vi.mock('@/lib/billing/core/billing-attribution', () => ({
1616
assertBillingAttributionSnapshot: vi.fn((value) => value),
17-
checkAttributedUsageLimits: vi.fn(),
1817
resolveBillingAttribution: vi.fn(),
1918
resolveSystemBillingAttribution: mockResolveSystemBillingAttribution,
2019
}))
20+
vi.mock('@/lib/billing/core/usage-gate-cache', () => ({
21+
checkExecutionUsageLimits: vi.fn(),
22+
}))
2123
vi.mock('@/lib/billing/core/subscription', () => ({
2224
getHighestPrioritySubscription: vi.fn(),
2325
}))

0 commit comments

Comments
 (0)