Skip to content

Commit 9b1d6ed

Browse files
authored
fix(knowledge): preserve organization billing context in document worker (#7596)
1 parent 90adc4e commit 9b1d6ed

2 files changed

Lines changed: 47 additions & 14 deletions

File tree

apps/sim/background/knowledge-processing.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ const WORKSPACE_PAYLOAD = {
7373
billingAttribution: BILLING_ATTRIBUTION,
7474
}
7575

76+
const ORGANIZATION_PAYLOAD = {
77+
...BASE_PAYLOAD,
78+
billingScope: 'organization' as const,
79+
actorUserId: 'organization-member',
80+
workspaceId: null,
81+
organizationId: 'organization-1',
82+
billingAttribution: {
83+
...BILLING_ATTRIBUTION,
84+
actorUserId: 'organization-member',
85+
workspaceId: null,
86+
organizationId: 'organization-1',
87+
billingEntity: { type: 'organization' as const, id: 'organization-1' },
88+
},
89+
}
90+
7691
function mockQuotaExhaustion(error: EmbeddingQuotaExhaustedError): void {
7792
mockProcessDocumentAsync.mockImplementation(async (...args: unknown[]) => {
7893
const attemptContext = args[6] as {
@@ -229,6 +244,36 @@ describe('knowledge processing worker', () => {
229244
)
230245
})
231246

247+
it('preserves organization ownership and billing attribution in the worker', async () => {
248+
await runDocumentProcessing(structuredClone(ORGANIZATION_PAYLOAD))
249+
250+
expect(mockProcessDocumentAsync).toHaveBeenCalledWith(
251+
BASE_PAYLOAD.knowledgeBaseId,
252+
BASE_PAYLOAD.documentId,
253+
BASE_PAYLOAD.docData,
254+
BASE_PAYLOAD.processingOptions,
255+
{
256+
billingScope: 'organization',
257+
actorUserId: ORGANIZATION_PAYLOAD.actorUserId,
258+
workspaceId: null,
259+
organizationId: ORGANIZATION_PAYLOAD.organizationId,
260+
billingAttribution: ORGANIZATION_PAYLOAD.billingAttribution,
261+
},
262+
BASE_PAYLOAD.requestId,
263+
expect.objectContaining({
264+
chargedAtDispatch: true,
265+
processingQueuedAt: new Date(BASE_PAYLOAD.processingQueuedAt),
266+
})
267+
)
268+
})
269+
270+
it('rejects an organization mismatch before document processing starts', async () => {
271+
await expect(
272+
runDocumentProcessing({ ...ORGANIZATION_PAYLOAD, organizationId: 'organization-2' })
273+
).rejects.toThrow('Document processing organization does not match billing attribution')
274+
expect(mockProcessDocumentAsync).not.toHaveBeenCalled()
275+
})
276+
232277
it('rejects an actor mismatch before document processing starts', async () => {
233278
await expect(
234279
runDocumentProcessing({

apps/sim/background/knowledge-processing.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
isUsageLimitDocumentProcessingError,
1313
} from '@/lib/knowledge/documents/document-processing-error'
1414
import {
15+
assertDocumentProcessingBillingContext,
1516
assertDocumentProcessingPayload,
16-
type DocumentProcessingBillingContext,
1717
type DocumentProcessingPayload,
1818
} from '@/lib/knowledge/documents/processing-payload'
1919
import {
@@ -34,19 +34,7 @@ export async function runDocumentProcessing(
3434
const startedAt = Date.now()
3535
const payload = assertDocumentProcessingPayload(rawPayload)
3636
const { knowledgeBaseId, documentId, docData, processingOptions, requestId } = payload
37-
const billingContext: DocumentProcessingBillingContext =
38-
payload.billingScope === 'workspace'
39-
? {
40-
billingScope: 'workspace',
41-
actorUserId: payload.actorUserId,
42-
workspaceId: payload.workspaceId,
43-
billingAttribution: payload.billingAttribution,
44-
}
45-
: {
46-
billingScope: 'non-workspace',
47-
actorUserId: payload.actorUserId,
48-
workspaceId: null,
49-
}
37+
const billingContext = assertDocumentProcessingBillingContext(payload)
5038
const canScheduleQuotaContinuation = canScheduleDocumentProcessingQuotaContinuation(payload)
5139

5240
logger.info(`[${requestId}] Starting Trigger.dev processing for document: ${docData.filename}`)

0 commit comments

Comments
 (0)