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 <fufenjupku@gmail.com>
This commit is contained in:
FenjuFu 2026-08-19 15:03:57 +08:00
parent d2403bb591
commit caf457f299

View file

@ -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);