fix: self-heal застрявших pending changes при broken project reference
- ensureConfigurationProject: если project не найден ни на сервере, ни локально (stale UUID после удаления), падаем в fallback «Без проекта» вместо вечной ошибки - PushPendingChanges: автоматически вызывает RepairPendingChanges() перед циклом, чтобы локально-исправимые проблемы чинились до попытки отправки - maxPendingChangeAttempts=20: после 20 неудачных попыток change считается unrecoverable и удаляется из очереди (логируется ERROR) - pushSingleChange/pushConfigurationChange: unknown entity type / operation теперь дропается с warn вместо вечного error в цикле - latestSyncErrorState: last_sync_error_text в qt_client_schema_state теперь содержит JSON-массив с type/uuid/op/attempts/error по всем застрявшим changes (до 20 штук) вместо текста только последней ошибки Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -320,14 +321,37 @@ func latestSyncErrorState(local *localdb.LocalDB) (*string, *string) {
|
||||
return optionalString(strings.TrimSpace(guard.ReasonCode)), optionalString(strings.TrimSpace(guard.ReasonText))
|
||||
}
|
||||
|
||||
var pending localdb.PendingChange
|
||||
var errored []localdb.PendingChange
|
||||
if err := local.DB().
|
||||
Where("TRIM(COALESCE(last_error, '')) <> ''").
|
||||
Order("id DESC").
|
||||
First(&pending).Error; err == nil {
|
||||
return optionalString("PENDING_CHANGE_ERROR"), optionalString(strings.TrimSpace(pending.LastError))
|
||||
Limit(20).
|
||||
Find(&errored).Error; err != nil || len(errored) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
type errorEntry struct {
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
Op string `json:"op"`
|
||||
Attempts int `json:"attempts"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
entries := make([]errorEntry, 0, len(errored))
|
||||
for _, ch := range errored {
|
||||
entries = append(entries, errorEntry{
|
||||
Type: ch.EntityType,
|
||||
UUID: ch.EntityUUID,
|
||||
Op: ch.Operation,
|
||||
Attempts: ch.Attempts,
|
||||
Error: strings.TrimSpace(ch.LastError),
|
||||
})
|
||||
}
|
||||
detail, jsonErr := json.Marshal(entries)
|
||||
if jsonErr != nil {
|
||||
return optionalString("PENDING_CHANGE_ERROR"), optionalString(strings.TrimSpace(errored[0].LastError))
|
||||
}
|
||||
return optionalString("PENDING_CHANGE_ERROR"), optionalString(string(detail))
|
||||
}
|
||||
|
||||
func optionalString(value string) *string {
|
||||
|
||||
Reference in New Issue
Block a user