From caf457f29959d6bcd277a4e022ed037ed5dbd732 Mon Sep 17 00:00:00 2001 From: FenjuFu Date: Wed, 19 Aug 2026 15:03:57 +0800 Subject: [PATCH] fix(security): publish scan task after transaction commit SecurityScanService.triggerScan is @Transactional but published the Redis Stream scan task inline, before the transaction committed. The stream consumer could receive the task before the skill_version / security_audit rows were visible, fail with "SkillVersion not found" / "SecurityAudit not found", exhaust its immediate retries while the publishing transaction was still open, and leave the committed version stuck in SCANNING. Defer the publish to an afterCommit transaction synchronization so the consumer only ever sees the task once the rows are committed and visible; on rollback the task is never published. Falls back to an inline publish when called outside a transaction. Closes #612 Signed-off-by: FenjuFu --- .../domain/security/SecurityScanService.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/security/SecurityScanService.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/security/SecurityScanService.java index 7a901f73..3549ec6a 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/security/SecurityScanService.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/security/SecurityScanService.java @@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import java.io.IOException; import java.nio.file.Files; @@ -75,7 +77,7 @@ public class SecurityScanService { } // Always create a new audit record — supports multiple rounds per version auditRepository.save(new SecurityAudit(versionId, ScannerType.SKILL_SCANNER)); - scanTaskProducer.publishScanTask(new ScanTask( + final ScanTask scanTask = new ScanTask( UUID.randomUUID().toString(), versionId, packagePath, @@ -83,7 +85,22 @@ public class SecurityScanService { publisherId, System.currentTimeMillis(), Map.of("scannerType", ScannerType.SKILL_SCANNER.getValue()) - )); + ); + // Publish the scan task only after this transaction commits, so the + // stream consumer cannot observe the task before the skill_version / + // security_audit rows are visible (issue #612). On rollback the task + // is never published. Fall back to inline publish if somehow called + // outside a transaction. + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + scanTaskProducer.publishScanTask(scanTask); + } + }); + } else { + scanTaskProducer.publishScanTask(scanTask); + } // Only transition to SCANNING if the version is not already published (auto-publish flow) if (version.getStatus() != SkillVersionStatus.PUBLISHED) { version.setStatus(SkillVersionStatus.SCANNING);