From 757c7227f87996de2d4ac4990508a4bbf66b6316 Mon Sep 17 00:00:00 2001 From: FFXN Date: Sat, 8 Aug 2026 16:18:23 +0800 Subject: [PATCH] fix(knowledge_fs): enhance error handling and logging for source compilation failures --- ...e-logical-document-version-adapter.test.ts | 19 ++++++++++ ...source-logical-document-version-adapter.ts | 3 +- .../source-product-workflow-runtime.test.ts | 36 +++++++++++++++++++ .../src/source-product-workflow-runtime.ts | 13 ++++++- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/knowledge-fs/packages/api/src/source-logical-document-version-adapter.test.ts b/knowledge-fs/packages/api/src/source-logical-document-version-adapter.test.ts index 32f6d17614d..d432e8a9bab 100644 --- a/knowledge-fs/packages/api/src/source-logical-document-version-adapter.test.ts +++ b/knowledge-fs/packages/api/src/source-logical-document-version-adapter.test.ts @@ -155,6 +155,25 @@ describe("Source compilation publication executor", () => { }, ); + it("preserves the durable compilation failure detail", async () => { + const fixture = compilationJobs([ + compilationJob({ + error: "Embedding model is unavailable", + runState: "failed", + stage: "failed", + }), + ]); + const executor = createSourceCompilationPublicationExecutor({ + compilationJobs: fixture.jobs, + maxWaitMs: 20, + pollIntervalMs: 1, + }); + + await expect(executor.publishAndWait(compilationInput())).rejects.toThrow( + "Source compilation terminated as failed: Embedding model is unavailable", + ); + }); + it("fails closed when the durable attempt disappears and tolerates cancellation cleanup failure", async () => { const fixture = compilationJobs([null], { cancelError: new Error("cancel unavailable") }); const executor = createSourceCompilationPublicationExecutor({ diff --git a/knowledge-fs/packages/api/src/source-logical-document-version-adapter.ts b/knowledge-fs/packages/api/src/source-logical-document-version-adapter.ts index c99cee355b9..9f858408d60 100644 --- a/knowledge-fs/packages/api/src/source-logical-document-version-adapter.ts +++ b/knowledge-fs/packages/api/src/source-logical-document-version-adapter.ts @@ -141,8 +141,9 @@ export function createSourceCompilationPublicationExecutor({ current.runState === "canceled" || current.runState === "superseded" ) { + const detail = current.error ? `: ${current.error}` : ""; throw new LogicalDocumentValidationError( - `Source compilation terminated as ${current.runState}`, + `Source compilation terminated as ${current.runState}${detail}`, ); } await cancellableDelay(pollIntervalMs, input.signal); diff --git a/knowledge-fs/packages/api/src/source-product-workflow-runtime.test.ts b/knowledge-fs/packages/api/src/source-product-workflow-runtime.test.ts index cc749d202a8..bef9e967ac7 100644 --- a/knowledge-fs/packages/api/src/source-product-workflow-runtime.test.ts +++ b/knowledge-fs/packages/api/src/source-product-workflow-runtime.test.ts @@ -834,6 +834,42 @@ describe("source-product workflow provider imports", () => { } }); + it("logs logical-revision publication failures and persists a specific workflow error", async () => { + const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); + const source = sourceRecord("publication-failure-source", { type: "connector" }); + const fixture = await createFixture({ + inventory: [], + onlineDocuments: { getPageContent: vi.fn(async () => ({ content: "content" })) }, + publishError: new Error("Source compilation terminated as failed: embedding unavailable"), + run: providerRun(source.id, "online-document-import", { + items: [ + { + pageId: "publication-page", + providerItemId: "publication-provider-item", + type: "page", + workspaceId: "publication-workspace", + }, + ], + }), + source, + }); + + await expect(fixture.runtime.tick()).resolves.toMatchObject({ completed: 0, failed: 1 }); + await expect(fixture.getRun()).resolves.toMatchObject({ + lastErrorCode: "SOURCE_DOCUMENT_COMPILATION_FAILED", + lastErrorMessage: "Source document compilation failed", + state: "failed", + }); + expect(consoleError).toHaveBeenCalledWith( + "Source workflow logical revision publication failed", + expect.objectContaining({ + error: "Source compilation terminated as failed: embedding unavailable", + sourceId: source.id, + }), + ); + consoleError.mockRestore(); + }); + it("runs the explicit crawl-import kind and cleans an empty frozen selection", async () => { const deleteRun = vi.fn(async () => ({ deleted: 0, hasMore: false })); const source = sourceRecord("empty-crawl-import", { type: "web" }); diff --git a/knowledge-fs/packages/api/src/source-product-workflow-runtime.ts b/knowledge-fs/packages/api/src/source-product-workflow-runtime.ts index 1faa7f652c1..576ec1c713b 100644 --- a/knowledge-fs/packages/api/src/source-product-workflow-runtime.ts +++ b/knowledge-fs/packages/api/src/source-product-workflow-runtime.ts @@ -1350,13 +1350,24 @@ async function publishLogicalRevisions( ), ); } catch (error) { + console.error("Source workflow logical revision publication failed", { + error: error instanceof Error ? error.message : String(error), + filename: candidate.filename, + knowledgeSpaceId: run.knowledgeSpaceId, + sourceId: source.id, + workflowId: run.id, + workflowKind: run.kind, + }); await input.materializer.compensate({ documents: [document], knowledgeSpaceId: run.knowledgeSpaceId, sourceId: source.id, tenantId: run.tenantId, }); - throw error; + throw runtimeError( + "SOURCE_DOCUMENT_COMPILATION_FAILED", + "Source document compilation failed", + ); } if (publication.kind === "unchanged") { await input.materializer.compensate({