feat: add durable provider cache
Add a private provider-neutral SQLite WAL cache with transactional migrations, retained raw payloads and tombstones, per-window opaque checkpoints, and reserved outbox/conflict tables. Verify migrations, permissions, identity invariants, concurrent access, atomic pull-page rollback, and future-schema rejection with deterministic tests. No OAuth, networking, secrets, or Graph synchronization is included yet.
This commit is contained in:
@@ -6,7 +6,7 @@ and terminal rendering replaceable.
|
||||
```text
|
||||
src/main.cpp
|
||||
├── tui/ ANSI rendering, input decoding, terminal lifecycle
|
||||
├── storage/ iCalendar file adapter (later: SQLite cache)
|
||||
├── storage/ iCalendar file adapter and provider-neutral cache
|
||||
└── domain/ Event, Calendar, civil dates, month layout and queries
|
||||
|
||||
future sync workers
|
||||
@@ -118,13 +118,27 @@ semantically. Browsing remains available for unsupported files. This
|
||||
conservative boundary is more important than partial editing because a
|
||||
successful-looking edit must not erase unrelated calendar data.
|
||||
|
||||
Provider synchronization will not write directly into this UI file. It will use
|
||||
a transaction-capable local cache and preserve remote ETags/sync tokens in a
|
||||
provider metadata table.
|
||||
Provider synchronization does not write directly into this UI file. Its
|
||||
prerequisite durable boundary is a provider-neutral SQLite database in WAL mode.
|
||||
The database is a private local file, and schema changes run as versioned,
|
||||
all-or-nothing migrations so a failed upgrade cannot leave a partially migrated
|
||||
cache.
|
||||
|
||||
The planned cache schema has `calendars`, `events`, `event_instances`, and
|
||||
`sync_state`. Raw provider payloads are retained alongside normalized fields so
|
||||
an older Nocal cannot destroy fields it does not understand.
|
||||
The cache separates provider records from normalized projections. It retains
|
||||
raw provider payloads alongside normalized fields so an older Nocal cannot
|
||||
destroy fields it does not understand. Inactive calendars and tombstoned events
|
||||
remain recorded rather than disappearing from history. Normalized event
|
||||
instances are materialized for immutable UI snapshots; provider payload details
|
||||
do not cross into rendering or the domain model.
|
||||
|
||||
Incremental progress is scoped by provider account, calendar, and synchronization
|
||||
window. A provider's opaque cursor is committed in the same transaction as each
|
||||
pulled page, preventing a crash from advancing the cursor beyond durable data.
|
||||
Transactional outbox and conflict records are reserved in the boundary for
|
||||
future local writes and deterministic conflict handling. This cache does not
|
||||
itself implement networking, authentication, or Microsoft Graph synchronization.
|
||||
OAuth tokens and other secrets are explicitly excluded from SQLite and belong
|
||||
in the Freedesktop Secret Service.
|
||||
|
||||
## Terminal backend
|
||||
|
||||
@@ -149,3 +163,10 @@ Service, never in the calendar database or command line.
|
||||
Conflict resolution is deterministic: unchanged side wins; otherwise retain
|
||||
both versions and mark a conflict for the user. Network work happens outside the
|
||||
render loop and publishes immutable snapshots to it.
|
||||
|
||||
The first provider-facing step after the cache is verified is an injectable HTTP
|
||||
boundary, browser-based authorization-code flow with PKCE, and fake transports
|
||||
for deterministic authentication and failure tests. Read-only Microsoft Graph
|
||||
calendar discovery and delta synchronization follows. Graph delta links remain
|
||||
opaque and are persisted only through the calendar/window transaction described
|
||||
above; remote writes and conflict resolution come later.
|
||||
|
||||
@@ -112,6 +112,29 @@ values incompatible with the event's `DTSTART` time basis or `TZID`, detached
|
||||
read-only. Remaining local-data work includes detached overrides and rule
|
||||
editing.
|
||||
|
||||
## Remote calendar foundation
|
||||
|
||||
Remote calendars must cross a durable local boundary before they are exposed to
|
||||
the UI. That boundary is a private, provider-neutral SQLite cache in WAL mode,
|
||||
with versioned migrations that either commit completely or leave the prior
|
||||
schema intact. It keeps raw provider payloads, inactive calendars, and
|
||||
tombstoned events so refreshes and older clients do not erase information. Each
|
||||
opaque incremental cursor belongs to a particular calendar and time window and
|
||||
is committed atomically with every pulled page. Normalized event instances feed
|
||||
immutable snapshots rather than exposing provider response types to the month
|
||||
view.
|
||||
|
||||
The cache also reserves transactional outbox and conflict records for future
|
||||
offline writes, but remote mutation behavior is not part of this phase. OAuth
|
||||
tokens and other credentials never belong in the calendar database; account
|
||||
secrets will be held by the Freedesktop Secret Service.
|
||||
|
||||
This foundation does not make Office 365 usable yet: Nocal currently performs no
|
||||
OAuth flow, HTTP request, or Microsoft Graph synchronization. The next product
|
||||
slice introduces injectable HTTP and PKCE authentication with fake transports,
|
||||
then adds read-only Graph delta synchronization. Remote writes remain gated on
|
||||
the durability and conflict paths being exercised end to end.
|
||||
|
||||
## Local file interchange
|
||||
|
||||
Import and export are explicit command-line workflows. They never contact the
|
||||
|
||||
@@ -24,12 +24,22 @@
|
||||
- Embedded VTIMEZONE support
|
||||
- Screen-reader-friendly linear view and full Unicode display-width handling
|
||||
|
||||
## 0.2 — durable cache and provider boundary
|
||||
## 0.2 — durable cache and provider boundary — in progress
|
||||
|
||||
- SQLite WAL cache and migration framework
|
||||
- Background job queue, immutable UI snapshots, observable sync status
|
||||
- Secret Service credentials and OAuth callback helper
|
||||
- Generic `SyncProvider` contract plus a fake provider test suite
|
||||
Completed cache foundation:
|
||||
|
||||
- Provider-neutral SQLite WAL cache in a private local file
|
||||
- Versioned, all-or-nothing schema migrations
|
||||
- Raw provider payload retention, inactive calendars, and event tombstones
|
||||
- Per-calendar/window opaque cursors committed atomically with pulled pages
|
||||
- Normalized event instances for immutable snapshots
|
||||
- Reserved transactional outbox and conflict records for later remote writes
|
||||
|
||||
Next provider-boundary slices:
|
||||
|
||||
- Injectable HTTP, PKCE authentication, and deterministic fake transports
|
||||
- Secret Service credentials; OAuth secrets are never stored in SQLite
|
||||
- Generic `SyncProvider` contract and observable sync status
|
||||
|
||||
## 0.3 — CalDAV
|
||||
|
||||
@@ -40,9 +50,15 @@
|
||||
## 0.4 — hosted providers
|
||||
|
||||
- Google Calendar adapter with incremental synchronization
|
||||
- Microsoft 365 adapter over Microsoft Graph delta queries
|
||||
- Microsoft 365 read-only discovery and Graph delta synchronization, followed by
|
||||
ETag-aware remote writes after the conflict boundary is proven
|
||||
- Account/calendar management UI and per-calendar ANSI identity
|
||||
|
||||
The current cache work is only the prerequisite durable boundary. No network,
|
||||
OAuth, or Graph synchronization is implemented in that phase. After the cache
|
||||
passes its verification gates, the immediate sequence is injectable HTTP plus
|
||||
PKCE and fake transports, then read-only Microsoft Graph delta synchronization.
|
||||
|
||||
## 1.0 — distribution quality
|
||||
|
||||
- Stable configuration/data format and documented recovery procedures
|
||||
|
||||
Reference in New Issue
Block a user