Let an event choose which source it is filed under
An event created in an aggregate went to the configured default_sink and nowhere else, so with several writable sources there was no way to say which calendar a new event belonged in. A line reading @endpoint-id in the description now picks the source, and a matching CATEGORIES value does too. The description rather than the title because every calendar client exposes a notes field and editing it does not disfigure the event's name; CATEGORIES as well because that is the field iCalendar intends, even though many mobile clients hide it. The marker is stripped before the event reaches the calendar, being calcalist's bookkeeping rather than content. A marker naming something that is not a writable source of that aggregate is refused and reported, not redirected to the default: a typo should not quietly file an event in the wrong calendar. A bare address in prose is not a marker either, since a marker must be a line of its own. Also covers the shapes beyond many-into-one: a source feeding several aggregates, several aggregates sharing one target, a cycle between two aggregates, a delete cascading across aggregates, and competing edits arriving through two aggregates at once — the last being caught by the existing conflict detection rather than silently overwriting. Error display no longer repeats itself; thiserror already prints the cause chain. Verified live against real accounts: two Google calendars aggregating into a Posteo calendar, an edit in the aggregate reaching the originating Google calendar, an event routed to a chosen source by its description marker, and a deletion propagating from the aggregate through to Google. 129 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
bc64f19211
commit
0c0558c24c
6 changed files with 506 additions and 12 deletions
13
TODO.md
13
TODO.md
|
|
@ -63,15 +63,22 @@ Tests:
|
|||
- [x] Retarget: drift makes `sync` exit non-zero having written nothing and losing no source
|
||||
event; purge is bounded by the derivation; an unrouted creation reaches a sink first
|
||||
|
||||
### Verified live, end to end
|
||||
|
||||
Two Google calendars aggregating into a Posteo CalDAV calendar, against real accounts:
|
||||
fan-in from both sources with provenance intact; an edit in the aggregate reaching the
|
||||
originating Google calendar; an event created in the aggregate routed to a chosen
|
||||
source by a description marker; deletion propagating from the aggregate through to
|
||||
Google; and the mass-deletion guard refusing a 100% removal until `--force`.
|
||||
|
||||
### Known gaps carried out of M1
|
||||
|
||||
- [ ] A recurring series' *exceptions* are not pushed to Google. Google models them
|
||||
as separate events against an already existing series, so they need
|
||||
`events.instances` plus a patch per exception. Reported per sync rather than
|
||||
dropped silently.
|
||||
- [ ] `push` deleting an event remotely is exercised only when the reconciler removes
|
||||
a mirror mid-cycle; it has no end-to-end test yet, because a pull legitimately
|
||||
resurrects anything deleted from a vdir before the cycle runs.
|
||||
- [x] `push` deleting an event remotely — verified live: deleting a mirror in the
|
||||
CalDAV aggregate removed the origin event from Google.
|
||||
- [ ] A failed Google pull aborts the whole cycle, including the CalDAV side. Safe —
|
||||
reconciling against a stale snapshot could read as mass deletion — but it means
|
||||
a lapsed token stops everything.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue