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:
@@ -1,5 +1,15 @@
|
||||
# Agendula — storage and sync
|
||||
|
||||
> ⚠️ **Partly superseded, 2026-08-13.** The core decision below — *vendor the
|
||||
> dmfs provider in-tree as `:provider`* — has been **reversed**. Agendula builds
|
||||
> its own Room store and deletes the vendored provider; External mode (OpenTasks,
|
||||
> tasks.org) is unaffected and everything this document says about it still
|
||||
> stands. See [`STORAGE-DECISION.md`](STORAGE-DECISION.md) for why and
|
||||
> [`OWN-STORE.md`](OWN-STORE.md) for what replaces it. The permissions,
|
||||
> distribution, storage-mode and dead-end sections below remain accurate; treat
|
||||
> the "our own provider" sections as the historical record of a decision that was
|
||||
> made, shipped, and then costed properly.
|
||||
|
||||
> Decided direction, captured 2026-08-01. Supersedes the earlier "Posture B =
|
||||
> bundle OpenTasks" working notes, which are withdrawn (see
|
||||
> [Dead ends](#dead-ends--do-not-revisit)). This is the detailed companion to
|
||||
@@ -34,7 +44,7 @@
|
||||
| 2 | Vendor `:provider` under our own authority | the identity, done once — and it ships a complete local-first app | ✅ done |
|
||||
| 3 | Export / backup | our data now lives only in our app's private storage | ✅ backend done; no UI yet |
|
||||
| 4 | File the DAVx5 issue | cheap, non-blocking, serves F-Droid users | ⬜ |
|
||||
| 5 | Sync adapter | the 1.x arc; design discussion pending | ⬜ |
|
||||
| 5 | Sync adapter | the 1.x arc; designed in [`SYNC.md`](SYNC.md), not yet built | ⬜ |
|
||||
|
||||
Everything below is the reasoning behind those choices, the alternatives that
|
||||
were rejected, and the constraints they have to survive.
|
||||
@@ -174,8 +184,13 @@ feature — see [Storage modes](#storage-modes--the-users-choice).
|
||||
| **Synced** | our bundled provider | our sync adapter | network + an account the user configures |
|
||||
| **External** | OpenTasks / tasks.org | whatever that provider's engine does (DAVx5 …) | that provider's `READ`/`WRITE_TASKS`, granted at runtime |
|
||||
|
||||
Local and Synced are the same store — Synced is Local with an account attached,
|
||||
so switching on sync is not a migration.
|
||||
Local and Synced are the same store — but ⚠️ **switching on sync *is* a
|
||||
migration, contrary to what this document said until 2026-08-13.** The provider
|
||||
enforces `ACCOUNT_NAME` and `ACCOUNT_TYPE` as **write-once** on a task list
|
||||
(`processors/lists/Validating.java:68-76`, which throws), so a list created under
|
||||
`org.dmfs.account.LOCAL` can never be re-pointed at a real account. Enabling sync
|
||||
means creating new lists under the account and moving tasks into them. See
|
||||
[`SYNC.md`](SYNC.md) — it is a costed deliverable there, not a free consequence.
|
||||
|
||||
**Resolver ordering — decided, and it went both ways as expected.**
|
||||
`ProviderResolver` now takes an explicit `StorageMode` from Settings when there
|
||||
@@ -235,10 +250,17 @@ compliance and a small enum-shaped addition with near-zero ongoing maintenance
|
||||
for them. Say that explicitly. "Here's a change that can't break anything" lands
|
||||
very differently from "please support my app."
|
||||
|
||||
**Open — the next discussion.** Protocol coverage ("support as much as
|
||||
possible"), the account model, conflict resolution, and where the DAV/iCalendar
|
||||
work lives. One constraint to settle early: we're MIT; `dav4jvm` is Apache-2.0
|
||||
and fine, but **verify `ical4android`'s license** before assuming it's usable.
|
||||
**The design is now written out in [`SYNC.md`](SYNC.md)** — protocol coverage,
|
||||
the account model, conflict resolution, the VTODO ↔ `TaskContract` mapper, and
|
||||
where the DAV/iCalendar work lives. Two corrections to what this section
|
||||
originally said, both verified 2026-08-13:
|
||||
|
||||
- `dav4jvm` is **MPL-2.0**, not Apache-2.0. Still fine against our MIT (file-level
|
||||
copyleft), but it is ⚠️ **JitPack-only**, which collides with our
|
||||
`FAIL_ON_PROJECT_REPOS` + `google()`/`mavenCentral()` policy and with the
|
||||
JitPack dead end below. `SYNC.md` open question 1.
|
||||
- `ical4android` is **superseded by `synctools`, which is GPLv3** — so it is
|
||||
unusable, and the open question below is closed. We write the mapper in-house.
|
||||
|
||||
---
|
||||
|
||||
@@ -336,6 +358,13 @@ Not on either yet; both are targets, so build *for* them rather than retrofittin
|
||||
throwaway spike* (a library string resource can be overridden from the app
|
||||
module, so the authority rename works), but not for anything we ship.
|
||||
|
||||
⚠️ **This one comes back.** It is a dead end *for the provider*, where
|
||||
vendoring was mandatory anyway. `dav4jvm` is JitPack-only, so the sync adapter
|
||||
has to answer the same question on its own terms — and F-Droid turns out not to
|
||||
be the obstacle (its inclusion policy trusts jitpack.io for freely-licensed
|
||||
artifacts); our own trust-surface policy is. See [`SYNC.md`](SYNC.md) open
|
||||
question 1.
|
||||
|
||||
---
|
||||
|
||||
## Sequencing
|
||||
@@ -358,8 +387,11 @@ the roadmap should say so rather than inheriting the old estimate.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. **Sync protocol coverage**, account model, conflict resolution — the next
|
||||
discussion. Still open.
|
||||
1. **Sync protocol coverage**, account model, conflict resolution — ✅ taken up in
|
||||
[`SYNC.md`](SYNC.md). The account model is answered there (`AccountManager`,
|
||||
which `PROVENANCE.md` change 1 already assumes); what stays open moves to that
|
||||
document's own list — dav4jvm's distribution, conflict policy, External mode's
|
||||
future, and recurring-task completion.
|
||||
2. **Resolver ordering** — ✅ decided, see
|
||||
[Storage modes](#storage-modes--the-users-choice). The **mode-selection UX**
|
||||
is still open: `autoMode()` picks a default, but the Settings override it
|
||||
@@ -376,9 +408,11 @@ the roadmap should say so rather than inheriting the old estimate.
|
||||
⚠️ **But that test is Robolectric, and it skips on ARM64**, where Robolectric
|
||||
has no SQLite backend in either mode. It runs on x86_64 CI. It is not a
|
||||
substitute for a device, and this remains on the device-verification list.
|
||||
4. **`ical4android` licensing** vs our MIT. Still open — and note the export path
|
||||
does *not* depend on it: `ICalendarWriter` is our own ~200 lines, no library.
|
||||
The question is really about the sync adapter's iCalendar *parsing*.
|
||||
4. ~~**`ical4android` licensing** vs our MIT.~~ ✅ Closed: `ical4android` is
|
||||
superseded by `synctools`, which is **GPLv3**, so it is out — as is
|
||||
`cert4android`. The export path never depended on it anyway (`ICalendarWriter`
|
||||
is our own ~200 lines). The sync adapter's iCalendar parsing goes through an
|
||||
in-house mapper over `ical4j`/`biweekly`; see [`SYNC.md`](SYNC.md).
|
||||
5. **jtx Board** as an additional External-mode candidate — richer contract,
|
||||
later. (`PLAN.md` decision #3, still open.)
|
||||
6. **The vendored provider's timezone-change behaviour** — upstream's receiver
|
||||
|
||||
Reference in New Issue
Block a user