fix: Pull-Wasserzeichen sprang über fremde Ereignisse + schärfere Push-Kollisionskontrolle

EventStore.Pull gab bisher den globalen ServerSeq-Höchststand als neuen Cursor zurück statt
den höchsten unter den tatsächlich gelieferten Ereignissen - hatte ein Gerät selbst kurz zuvor
etwas gepusht, sprang sein Pull-Cursor über noch nicht abgeholte Ereignisse anderer Geräte
hinweg und verpasste sie dauerhaft, ohne jeden Fehler.

Ersetzt außerdem die bisherige 30-Sekunden-Heuristik zur Konflikterkennung beim Push durch
exakte BasedOnServerSeq-Prüfung: jedes SyncEvent trägt die ServerSeq, auf der es aufbaut: der
Server lehnt ab, wenn der aktuelle Stand nicht mehr passt. Bei Ablehnung lädt der Client sofort
den neuen Server-Stand nach, löst den Konflikt nach der bestehenden Desktop-vs-Companion/
Timestamp-Politik auf und macht ihn immer in der Konflikt-Review-UI sichtbar, statt die
verworfene Änderung stillschweigend zu verlieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 23:25:04 +02:00
co-authored by Claude Sonnet 5
parent 270c4d40cd
commit 5bd6967421
13 changed files with 704 additions and 22 deletions
+11
View File
@@ -47,6 +47,17 @@ public static class Endpoints
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return uid is null ? Results.Unauthorized() : Results.Ok(new { userId = uid, timestamp = DateTime.UtcNow });
});
// Für Clients, deren Push wegen eines neueren Server-Stands abgelehnt wurde (10.3.4-
// Nachtrag: exakte Kollisionsprüfung statt 30s-Heuristik) — sofortiges Nachladen des
// aktuellen Stands EINER Entität, statt auf den nächsten regulären Pull zu warten.
g.MapGet("/entity/{entityType}/{entityId}", (string entityType, string entityId,
ClaimsPrincipal user, EventStore store) =>
{
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (uid is null) return Results.Unauthorized();
var result = store.GetLatestForEntity(uid, entityType, entityId);
return result is null ? Results.NotFound() : Results.Ok(result);
});
}
// ── Anhänge (eigener Binärkanal, getrennt vom JSON-Ereigniskanal) ──────────