From b68289f24aabece00a58d996471efc7d83f0ddac Mon Sep 17 00:00:00 2001 From: FFXN Date: Thu, 6 Aug 2026 14:40:30 +0800 Subject: [PATCH] fix: prevent source deletion failures caused by UUID and text type mismatch --- api/knowledge-fs-contract.lock.json | 2 +- ...rable-deletion-target-capabilities.test.ts | 31 +++++++++++++++++++ ...se-durable-deletion-target-capabilities.ts | 8 +++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/api/knowledge-fs-contract.lock.json b/api/knowledge-fs-contract.lock.json index 21b89c39e37..e2410ec0453 100644 --- a/api/knowledge-fs-contract.lock.json +++ b/api/knowledge-fs-contract.lock.json @@ -1,6 +1,6 @@ { "schemaVersion": 5, - "subtreeTree": "42fa6cb607aff009cc84df50b2f92e90b05dcc4c", + "subtreeTree": "ffffd2c76ac649416f17a1340f73c2da042a99f4", "openapiSha256": "2dbc5f58e4300f2dd9885a4867f02d0f3b2a65cd7b2b067a1ee6ba3cbd5210d3", "capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7", "capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3", diff --git a/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.test.ts b/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.test.ts index b24fedac18a..fe85f78b1c0 100644 --- a/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.test.ts +++ b/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.test.ts @@ -1892,6 +1892,37 @@ describe("database durable deletion target capabilities", () => { ).toBe(true); }); + it(`uses a UUID source predicate while deleting source Overview residue (${dialect})`, async () => { + const calls: DatabaseExecuteInput[] = []; + const execute = async (input: DatabaseExecuteInput): Promise => { + calls.push(input); + return result([]); + }; + + await capabilitiesFor(dialect, execute).deleteDerivedDataPage({ + job: job({ targetType: "source" }), + limit: 7, + signal: new AbortController().signal, + }); + + const overviewQueries = calls.filter( + (call) => + call.operation === "select" && + (call.tableName === "knowledge_space_attention_states" || + call.tableName === "knowledge_space_activity_events") && + call.sql.includes("logical_documents"), + ); + expect(overviewQueries.length).toBeGreaterThan(0); + for (const query of overviewQueries) { + const targetPosition = query.params.length === 4 ? 4 : 3; + expect(query.sql).toContain( + dialect === "postgres" + ? `"source_id" = CAST($${targetPosition} AS UUID)` + : "`source_id` = ?", + ); + } + }); + it(`removes only an unpublished aggregate exactly owned by the deleted asset (${dialect})`, async () => { const prefix = `tenant-a/spaces/${spaceId}`; const rawObjectKey = `${prefix}/documents/${targetDocumentId}/raw.md`; diff --git a/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.ts b/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.ts index 8aa1a8ab9f5..eaf353b60e0 100644 --- a/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.ts +++ b/knowledge-fs/packages/api/src/database-durable-deletion-target-capabilities.ts @@ -833,7 +833,9 @@ async function deleteOverviewResiduePage( database.dialect === "postgres" ? `CAST(${q("id")} AS TEXT)` : `CAST(${q("id")} AS CHAR(36))` - } FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${p(4)})))`; + } FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${ + database.dialect === "postgres" ? `CAST(${p(4)} AS UUID)` : p(4) + })))`; } else if (job.targetType === "logical_document") { params.push(job.targetId); targetPredicate = ` AND ${q("resource_type")} = 'document' AND ${q("resource_id")} = ${p(4)}`; @@ -885,7 +887,9 @@ async function hasOverviewResidue( database.dialect === "postgres" ? `CAST(${q("id")} AS TEXT)` : `CAST(${q("id")} AS CHAR(36))` - } FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${p(3)})))`; + } FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${ + database.dialect === "postgres" ? `CAST(${p(3)} AS UUID)` : p(3) + })))`; } else if (job.targetType === "logical_document") { params.push(job.targetId); targetPredicate = ` AND ${q("resource_type")} = 'document' AND ${q("resource_id")} = ${p(3)}`;