diff --git a/internal/store/sync_store.go b/internal/store/sync_store.go index a4e9d49..fc7ac6d 100644 --- a/internal/store/sync_store.go +++ b/internal/store/sync_store.go @@ -36,8 +36,9 @@ type SyncChange struct { } // Pull returns all sync_log rows with version > sinceVersion. -// It calls Prune first with a 30-day TTL. -// If the client is behind a prune marker it returns ErrSyncStale. +// It calls Prune first with a 30-day TTL. A zero-version pull is a bootstrap +// and may proceed past the prune marker; non-zero clients behind the marker +// must perform a full re-sync. func (s *SyncStore) Pull(ctx context.Context, sinceVersion int64) ([]SyncChange, int64, error) { if err := s.Prune(ctx, 30*24*time.Hour); err != nil { return nil, 0, err @@ -58,9 +59,11 @@ func (s *SyncStore) Pull(ctx context.Context, sinceVersion int64) ([]SyncChange, if err := rows.Scan(&c.Entity, &c.EntityID, &c.Op, &c.Version, &c.Payload); err != nil { return nil, 0, err } - // First row with entity="_pruned" means client is stale. if c.Entity == pruneEntity { - return nil, 0, ErrSyncStale + if sinceVersion != 0 { + return nil, 0, ErrSyncStale + } + continue } if c.Version > maxVersion { maxVersion = c.Version diff --git a/internal/store/sync_store_test.go b/internal/store/sync_store_test.go index 5da33ef..c160f51 100644 --- a/internal/store/sync_store_test.go +++ b/internal/store/sync_store_test.go @@ -76,8 +76,8 @@ func TestSyncPruneStaleClient(t *testing.T) { t.Fatalf("Prune: %v", err) } - // A stale client (since=0) should get ErrSyncStale. - _, _, err := s.Pull(ctx, 0) + // A client with an existing sync position behind the marker is stale. + _, _, err := s.Pull(ctx, 1) if !errors.Is(err, store.ErrSyncStale) { t.Fatalf("expected ErrSyncStale, got %v", err) }