fix(processing): clear replay error state

人工重放把 FAILED/DEAD 拨回 PENDING 时,与 attempts/next_attempt_at 一起清空 error_class 与 last_error,使活动状态不携带上一轮失败结论;identity_key、白名单与 MessageLifecycleGate 不变。

同语句同时改 JDBC 与内存实现,避免两套行为漂移。验证:ReplayServiceTest 3/0、InboxLifecycleJdbcSqlTest 8/0(H2 真实 SQL)。
This commit is contained in:
windyboy
2026-09-12 20:32:27 +08:00
parent eb103d6244
commit a89755c200
4 changed files with 38 additions and 4 deletions
@@ -276,7 +276,8 @@ class JdbcProcStateRepository(
if (errorClasses.isEmpty()) return 0
val placeholders = errorClasses.joinToString(",") { "?" }
return ds.update(
"UPDATE proc_state SET state = 'PENDING', attempts = 0, next_attempt_at = NULL, updated_at = ? " +
"UPDATE proc_state SET state = 'PENDING', attempts = 0, next_attempt_at = NULL, " +
"error_class = NULL, last_error = NULL, updated_at = ? " +
"WHERE state IN ('FAILED', 'DEAD') AND error_class IN ($placeholders)",
{ ps ->
ps.setTimestamp(1, clock.instant().toSqlTimestamp())
@@ -210,7 +210,10 @@ class StubProcState : ProcStateRepository {
var n = 0
rows.forEach { (id, s) ->
if (s.errorClass in errorClasses && (s.state == ProcStatus.FAILED || s.state == ProcStatus.DEAD)) {
rows[id] = s.copy(state = ProcStatus.PENDING, attempts = 0, nextAttemptAt = null)
rows[id] = s.copy(
state = ProcStatus.PENDING, attempts = 0, nextAttemptAt = null,
errorClass = null, lastError = null,
)
n++
}
}