feat(sync): reconcile secondary Graph calendars

Read documented Graph v1 calendarView pages for secondary calendars and publish each finite window only after the complete bounded listing validates.

Add schema-v2 per-window event membership so moved-out occurrences are evicted without being mislabeled as remote tombstones, while overlapping windows retain shared instances.

Verified: 18/18 fresh normal, 18/18 -Werror, and 18/18 ASan/UBSan tests.
This commit is contained in:
2026-07-18 13:05:21 +01:00
parent 0582444c61
commit 98e07e287e
11 changed files with 1520 additions and 60 deletions

View File

@@ -76,6 +76,16 @@ struct PullPage {
bool operator==(const PullPage&) const = default;
};
// A provider's complete view of one finite calendar window. Membership is
// replaced only after the provider listing has completed successfully.
struct CompleteWindow {
std::vector<CachedEvent> events;
std::vector<CachedEventInstance> instances;
SyncCheckpoint checkpoint;
bool operator==(const CompleteWindow&) const = default;
};
struct CacheSnapshot {
std::vector<CachedAccount> accounts;
std::vector<CachedCalendar> calendars;
@@ -88,7 +98,7 @@ struct CacheSnapshot {
class SyncCache {
public:
static constexpr int current_schema_version = 1;
static constexpr int current_schema_version = 2;
explicit SyncCache(std::filesystem::path path);
~SyncCache();
@@ -112,6 +122,12 @@ public:
// events retain their last raw payload and have no instances.
void apply_pull_page(const PullPage& page);
// Atomically replaces membership for one complete, finite provider window.
// Returned events become live and have their instances replaced. Events
// omitted from the window lose cached instances only when no other complete
// window references them; omission is not treated as a remote tombstone.
void replace_complete_window(const CompleteWindow& window);
[[nodiscard]] CacheSnapshot snapshot() const;
private: