@@ -24,7 +24,6 @@ import {
2424import { knowledgeOperations } from '@/lib/knowledge/application/operations'
2525import {
2626 findKnowledgeImport ,
27- finishKnowledgeImport ,
2827 getKnowledgeImport ,
2928 insertKnowledgeImport ,
3029 KNOWLEDGE_IMPORT_STORAGE_CONTEXT ,
@@ -37,7 +36,6 @@ import {
3736 runKnowledgeImport ,
3837} from '@/lib/knowledge/transfer/import-runner'
3938import { assertWorkspaceCapability } from '@/lib/permission-groups/capability-assertions'
40- import { deleteFile } from '@/lib/uploads/core/storage-service'
4139import { requestOrigin } from '@/lib/uploads/upload-session/application'
4240import {
4341 abortUploadSession ,
@@ -101,11 +99,6 @@ interface KnowledgeImportResult {
10199 import : KnowledgeImportResource
102100}
103101
104- interface CompleteKnowledgeImportResult extends KnowledgeImportResult {
105- /** The base this completion started importing, or `null` when the import was already started. */
106- startedKnowledgeBaseId : string | null
107- }
108-
109102interface KnowledgeImportRecordContext extends KnowledgeWorkspaceContext {
110103 record : KnowledgeImportRecord
111104}
@@ -270,33 +263,13 @@ function startKnowledgeImport(payload: KnowledgeImportPayload): Promise<void> {
270263 } )
271264}
272265
273- /** Starts the runner; a dispatch that fails settles the import and drops its bundle. */
274- async function dispatchKnowledgeImport ( payload : KnowledgeImportPayload ) : Promise < void > {
275- try {
276- await startKnowledgeImport ( payload )
277- } catch ( error ) {
278- await finishKnowledgeImport ( payload . importId , {
279- status : 'failed' ,
280- error : 'The import could not be started' ,
281- } )
282- await deleteFile ( { key : payload . sourceKey , context : KNOWLEDGE_IMPORT_STORAGE_CONTEXT } ) . catch (
283- ( cleanupError : unknown ) => {
284- logger . error ( 'Failed to delete a knowledge bundle after dispatch failure' , {
285- importId : payload . importId ,
286- error : getErrorMessage ( cleanupError , 'Unknown error' ) ,
287- } )
288- }
289- )
290- throw error
291- }
292- }
293-
294266/**
295267 * Dispatches again an import whose record no worker has claimed: its first
296- * dispatch was lost with the request that wrote the record, or is still
297- * queued. Either way a second run is safe, since only one worker can claim it,
298- * and a failure here leaves the import to the stale-import janitor rather than
299- * failing one that may already be on its way.
268+ * dispatch failed or was lost with the request that wrote the record, or is
269+ * still queued. Either way a second run is safe, since only one worker can
270+ * claim it. A dispatch never settles the import itself, since another one may
271+ * already be on its way; one that never runs is failed by the stale-import
272+ * janitor, which also deletes its bundle.
300273 */
301274async function redispatchUnclaimedImport (
302275 principal : Principal ,
@@ -399,11 +372,11 @@ export const completeKnowledgeImport = defineAuthorizedKnowledgeUseCase({
399372 principal : Principal
400373 input : KnowledgeImportUploadInput
401374 } ) => resolveUploadContext ( principal , input ) ,
402- async execute ( { principal, context } ) : Promise < CompleteKnowledgeImportResult > {
375+ async execute ( { principal, context } ) : Promise < KnowledgeImportResult > {
403376 const existing = await findStartedImport ( context )
404377 if ( existing ) {
405378 await redispatchUnclaimedImport ( principal , context , existing )
406- return { import : fromRecord ( existing ) , startedKnowledgeBaseId : null }
379+ return { import : fromRecord ( existing ) }
407380 }
408381 let started : KnowledgeImportPayload | null
409382 try {
@@ -427,11 +400,11 @@ export const completeKnowledgeImport = defineAuthorizedKnowledgeUseCase({
427400 // A concurrent completion holds the session; report the import it started, if any.
428401 const raced =
429402 asOrchestrationError ( error ) ?. code === 'conflict' ? await findStartedImport ( context ) : null
430- if ( raced ) return { import : fromRecord ( raced ) , startedKnowledgeBaseId : null }
403+ if ( raced ) return { import : fromRecord ( raced ) }
431404 throw error
432405 }
433406 if ( started ) {
434- await dispatchKnowledgeImport ( started )
407+ await startKnowledgeImport ( started )
435408 logger . info ( 'Started knowledge import' , {
436409 importId : started . importId ,
437410 workspaceId : context . workspaceId ,
@@ -442,20 +415,21 @@ export const completeKnowledgeImport = defineAuthorizedKnowledgeUseCase({
442415 importId : context . upload . id ,
443416 assertedWorkspaceId : context . workspaceId ,
444417 } )
445- return { import : fromRecord ( record ) , startedKnowledgeBaseId : started ?. importId ?? null }
418+ return { import : fromRecord ( record ) }
446419 } ,
447- /** Recorded once per import: a repeated completion only reads back the one already started. */
448- projectAudit : ( { context, result } ) =>
449- result . startedKnowledgeBaseId
450- ? {
451- action : AuditAction . KNOWLEDGE_BASE_IMPORTED ,
452- resourceType : AuditResourceType . KNOWLEDGE_BASE ,
453- resourceId : result . startedKnowledgeBaseId ,
454- resourceName : importedName ( context . upload ) ?? context . upload . fileName ,
455- description : `Imported a knowledge base from bundle "${ context . upload . fileName } "` ,
456- metadata : { workspaceId : context . workspaceId , importId : result . import . id } ,
457- }
458- : [ ] ,
420+ /**
421+ * Keyed by the import, so the completion that started it and any retry of it
422+ * record one event between them. The base takes the import's id.
423+ */
424+ projectAudit : ( { context, result } ) => ( {
425+ action : AuditAction . KNOWLEDGE_BASE_IMPORTED ,
426+ resourceType : AuditResourceType . KNOWLEDGE_BASE ,
427+ resourceId : result . import . id ,
428+ resourceName : importedName ( context . upload ) ?? context . upload . fileName ,
429+ description : `Imported a knowledge base from bundle "${ context . upload . fileName } "` ,
430+ metadata : { workspaceId : context . workspaceId , importId : result . import . id } ,
431+ idempotencyKey : `knowledge-import:${ result . import . id } ` ,
432+ } ) ,
459433} )
460434
461435/**
@@ -473,6 +447,10 @@ export const cancelKnowledgeImport = defineAuthorizedKnowledgeUseCase({
473447 const started = await findStartedImport ( context )
474448 if ( started ) return { import : fromRecord ( await stopKnowledgeImport ( started ) ) }
475449 assertUploadSessionAuthBinding ( context . upload , principal )
476- return { import : fromUpload ( await abortUploadSession ( context . upload ) , context . workspaceId ) }
450+ const aborted = await abortUploadSession ( context . upload )
451+ // A completion finalizing alongside this abort may have written the record first.
452+ const late = await findStartedImport ( context )
453+ if ( late ) return { import : fromRecord ( await stopKnowledgeImport ( late ) ) }
454+ return { import : fromUpload ( aborted , context . workspaceId ) }
477455 } ,
478456} )
0 commit comments