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

@@ -177,8 +177,9 @@ Microsoft authentication is designed for a public desktop client. It uses the
system browser and authorization-code flow, a fresh PKCE verifier and `S256`
challenge, and a loopback redirect. The Entra application registration is
multitenant and selects the intended organizational and personal Microsoft
account audience. The initial read-only Graph scope is the delegated
`Calendars.Read` permission. It normally permits user consent, while tenant
account audience. The read-only Graph scopes are delegated `User.Read` for
identity and `Calendars.Read` for calendar data. They normally permit user
consent, while tenant
consent policy remains authoritative and may require administrator approval or
block consent. A desktop binary distributed as open source is not a confidential
client and must not embed or depend on a client secret.
@@ -268,20 +269,25 @@ credential-bearing response material. Requests use only HTTPS
values are opaque continuation capabilities: Nocal validates their scheme and
host but does not reconstruct or reinterpret their query parameters.
Event synchronization uses half-open UTC windows. Each page's normalized
events, deletion tombstones, and opaque next/delta cursor commit in one cache
transaction. A partial round is therefore durable and resumable, while a cursor
can never advance beyond its corresponding event changes.
Event synchronization uses half-open UTC windows. For the primary calendar,
each delta page's normalized events, deletion tombstones, and opaque cursor
commit in one cache transaction. A partial round is durable and resumable, and
the cursor cannot advance beyond its corresponding changes.
Stable Microsoft Graph v1.0 documents `calendarView` delta only for the primary
calendar. The adapter discovers all calendars but delta-syncs events only from
the primary calendar. It does not call beta or an undocumented per-calendar
delta route. Secondary calendars remain an explicit architecture decision:
either reconcile complete v1 calendar-view windows with well-defined deletion
semantics, or accept a beta-specific delta dependency. That choice must be made
and tested before Nocal claims Outlook-equivalent calendar coverage.
Secondary-calendar reconciliation through stable Microsoft Graph v1.0 uses the
documented per-calendar `calendarView` route, accumulates every page within
fixed total bounds, and publishes only a completely validated listing. Schema
version 2 tracks durable event membership by calendar and window. Replacing one
window upserts its returned events and removes instances for omitted events only
when no other complete window still references them. The event envelope remains
live and its raw provider payload is retained, because absence can mean that an
event moved outside the requested range rather than that it was deleted
remotely.
This design avoids beta and undocumented per-calendar delta routes. Its
tradeoff is bandwidth: every secondary window is fetched completely on each
refresh, while the primary calendar remains incremental.
There is no account UI or CLI orchestration and no live Microsoft credential
integration yet; tests use injected transports and credentials. Remote writes
remain out of scope. Account setup and orchestration plus the secondary-calendar
reconciliation decision are next.
remain out of scope. Account setup and orchestration are next.

View File

@@ -133,8 +133,9 @@ The Microsoft sign-in design treats Nocal as a public desktop client: sign-in
will open the system browser, use the authorization-code flow with PKCE `S256`,
and return through a loopback redirect. Its application registration must be
multitenant for the intended organizational and personal Microsoft account
audience. Initial synchronization requests only delegated `Calendars.Read`, not
write access. Users can normally consent to that delegated permission, but
audience. Initial synchronization requests delegated `User.Read` and
`Calendars.Read`, not write access. Users can normally consent to those
delegated permissions, but
their organization's tenant policy may still require administrator consent or
deny the application. Nocal never embeds a client secret in its open-source
desktop binary.
@@ -183,21 +184,21 @@ calendar a deterministic local ID. Event reads request immutable Graph IDs and
use strict bounded parsing with credential-safe failures. Requests and opaque
next/delta links are restricted to HTTPS `graph.microsoft.com` URLs.
Event windows are half-open UTC intervals. Each event page, its deletions, and
its continuation cursor cross the cache boundary in one transaction. A partial
round can resume from its durable cursor, and deletion tombstones remain
explicit instead of silently dropping remote state.
Event windows are half-open UTC intervals. Primary-calendar delta pages cross
the cache boundary with their deletions and continuation cursor in one
transaction, so a partial round resumes safely and tombstones remain explicit.
The stable Graph v1.0 `calendarView` delta API is documented only for the
primary calendar. This slice discovers all calendars but delta-syncs only the
primary; it uses neither beta nor undocumented per-calendar routes. Secondary
calendar support requires a deliberate choice between v1 full-window
reconciliation with defined deletion semantics and beta-specific delta. Nocal
does not yet claim Outlook-equivalent coverage.
Stable-v1 secondary-calendar reconciliation reads every page of a complete
per-calendar view before publishing the window. Durable per-window membership
distinguishes an event that moved outside the range from an event Graph actually
deleted: an omitted event loses its cached instance only when no other complete
window references it, while its raw event record remains live. This avoids beta
and undocumented delta routes, but a secondary window requires a complete
refetch on every refresh.
There is still no account UI/CLI orchestration or live Microsoft credential
integration, and remote writes remain out of scope. Account setup/orchestration
and the secondary-calendar reconciliation decision are the next product phase.
is the next product phase.
## Local file interchange

View File

@@ -84,11 +84,18 @@ Completed Microsoft Graph read phase:
- Half-open UTC event windows and per-page event/tombstone/cursor transactions
- Resumable primary-calendar v1 delta synchronization
Completed secondary-calendar stable-v1 reconciliation:
- Documented per-calendar `calendarView` reads with complete bounded paging
- Schema-v2 durable per-window event membership
- Atomic complete-window replacement after every page validates
- Range omission distinguished from a remote deletion tombstone
- Overlapping windows retain events until their last membership is removed
- No beta or undocumented per-calendar delta dependency
Next provider-boundary slices:
- Account setup and CLI/TUI orchestration with live credential integration
- Choose secondary-calendar behavior: v1 full-window reconciliation with
deletion semantics, or an explicit beta-specific delta dependency
- Generic `SyncProvider` integration and observable sync status
## 0.3 — CalDAV
@@ -104,12 +111,11 @@ Next provider-boundary slices:
ETag-aware remote writes after the conflict boundary is proven
- Account/calendar management UI and per-calendar ANSI identity
The Graph read phase discovers all calendars but uses stable v1 delta only for
the primary calendar; it deliberately avoids beta and undocumented
per-calendar routes. No account UI/CLI orchestration or live Microsoft
credential integration exists yet, and remote writes remain out of scope. Do
not claim Outlook-equivalent coverage until the secondary-calendar strategy is
chosen and verified.
The Graph read boundary uses stable v1 delta for the primary calendar and
complete stable-v1 calendar views for secondary calendars; it deliberately
avoids beta and undocumented per-calendar delta routes. No account UI/CLI
orchestration or live Microsoft credential integration exists yet, and remote
writes remain out of scope.
## 1.0 — distribution quality