docs: decide to build our own store and delete the vendored provider

The vendored dmfs provider was kept on the grounds that it hands us the
sync bookkeeping for free. The phase-1 sync audit measured that
bookkeeping and found most of it broken, absent, or unusable: _DIRTY not
set on delete, no home for a per-collection sync token, read-only
collections inexpressible, ACCOUNT_TYPE write-once so enabling sync is a
full migration, and cleanUpLists able to delete a user's lists after a
backup restore. Sixteen findings are provider-imposed rather than
platform- or protocol-imposed.

Costing the alternative showed the swap is far smaller than assumed.
TasksDataSource is already a 14-method, domain-shaped interface;
exactly one file above the data layer references TasksContract. The
work is a second implementation behind an interface built for it, not a
rewrite. Against ~5 weeks to build, owning the store removes 2.5-4
weeks from the sync plan, and 8,200 of the vendored 14,555 lines are
things we would never write - 23 migrations from a 2013 schema, 798
lines of full-text search the app has zero call sites for, and 1,581
lines of a type-safe layer over ContentValues that Room deletes.

External mode (OpenTasks, tasks.org) is unaffected and keeps every
file that describes somebody else's schema.

STORAGE-DECISION.md is the reasoning; OWN-STORE.md is the architecture
and the six-phase plan. :provider stays in-tree until phase 5 so
recurrence parity can be tested against it before it goes.

Also corrected here: the provider's JVM test count (51 -> 56, measured
from the test-results XML) and a fourth site of the debunked "switching
sync on is never a migration" claim, in StorageMode.kt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-13 14:46:07 +02:00
parent 98ed339346
commit 13cb27b2ab
10 changed files with 1984 additions and 39 deletions

View File

@@ -22,7 +22,7 @@ the original "owns no database" thesis, settled in
installed made someone else's roadmap a gate on the app working at all. The
database is the vendored dmfs provider under *our* authority — our namespace, not
a schema written from scratch — so every CalDAV engine still understands it. A
sync adapter of our own is the 1.x arc.
sync adapter of our own is the 1.x arc, designed in [`SYNC.md`](SYNC.md).
The whole design hangs off one rule:
@@ -143,9 +143,15 @@ The platform calls sit behind `ProviderEnvironment` so this decision is unit
tested on the JVM (`ProviderResolverTest`) rather than only on a device.
**Storage modes** are `LOCAL` and `EXTERNAL` only. `STORAGE-AND-SYNC.md`
describes three, but *Synced* is not a third store — it is Local with an account
attached, so it is derived state, and modelling it as a separate mode would imply
that turning sync on is a migration. It isn't.
describes three, but *Synced* is not a third store — it is the same store with an
account attached, so it stays derived state.
⚠️ **What this used to say — "so turning sync on is not a migration" — is wrong.**
A task list's `ACCOUNT_NAME`/`ACCOUNT_TYPE` are write-once in the provider
(`processors/lists/Validating.java:68-76`), so local lists cannot be re-pointed at
an account; tasks have to be moved into new lists. Not modelling `SYNCED` as a
mode is still right, but the migration it implied away is real. See
[`SYNC.md`](SYNC.md).
### 4.2 `TasksContract`
@@ -261,7 +267,9 @@ resolver, so vendoring an entire content provider **changed no UI, no ViewModel,
no domain type, and not one line of `TasksRepository`.**
Bundling the provider bundles **storage, not sync**. Our own sync adapter is a
separate, later piece of work — see `STORAGE-AND-SYNC.md`.
separate, later piece of work — designed in [`SYNC.md`](SYNC.md), and it lands
*underneath* this same seam: it writes through `TaskContract` with
`CALLER_IS_SYNCADAPTER`, so the layers above it stay untouched a second time.
---