Withdrawal read published, which is in-memory, so the second loop only
ever withdrew reminders this process had published. Fire a reminder,
restart mavcaldav, and its event stayed in the collection forever with
nothing left to revisit it. "Losing it costs nothing, the next tick
rebuilds it" holds for events that should be there and not for the ones
that should not.
The first tick now PROPFINDs the collection and reconciles what it finds
against what is pending. Only hrefs carrying ReminderUIDPrefix are read
back, so the pass can never propose deleting a file maven did not create.
A failed read is retried on the next tick rather than skipped for the
life of the process.
Two smaller things from the same review. checkRenderTarget takes the
whole read set, so a second calendar to read cannot quietly fall outside
the guarantee the package comment makes. writeIfChanged loses its
confidence parameter, which every caller passed 1.0 and nothing read.
Found in review of #56.
Radicale becomes a write-only render target, not a store. sqlite stays
canonical: every poll mavcaldav reads the pending reminders out of core and
publishes each one as a single-event iCal resource, withdrawing the ones that
have fired or been cancelled. Losing the collection costs nothing — the next
tick rebuilds it, and nothing is ever read back from it.
It structurally cannot write to a calendar maven only reads. The render URL and
credential are their own flags, and -render-url is refused at startup when it
names the collection -url reads; the only paths it addresses carry the
maven-reminder- prefix, so even aimed at the wrong collection it can only touch
resources it created. Rendering is off unless -render-url is given.
The calendar data model now lives in one place, internal/calendar: the Event,
the iCal parse it comes from and the render it goes to, the fact key/value
encoding, and the source constants that say which calendars may be written to.
It was a parse inlined in cmd/mavcaldav and a Sprintf in two files; #126 and
#128 both need to agree with it.
Fixes a latent day-boundary bug moved out of that inline parse: it took the day
number off a local clock reading but built the window boundaries in UTC, so on
a box east of Greenwich part of the evening fell outside "today" and the poller
saw an empty calendar after 20:00 UTC. Today is now the owner's day in the
owner's location, which is what the busy gate and the day plan mean.
The cold-start crash-loop wasn't mavweb-specific — mavpoll and mavcaldav also
ipc.Dial + exit on failure, so they crash-looped until core booted too. Moved
the retry into ipc.DialWait (capped backoff, bounded) and switched mavweb,
mavpoll, mavcaldav to it. mavweb's local dialCoreWithRetry is gone.
Test: server appears after DialWait starts → it waits and connects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Polls Radicale for today's events, writes calendar_busy and calendar_event
facts through CoreAPI. Only writes on value change (same append-only
discipline as mavpoll).
Usage: mavcaldav -socket <core> -url <radicale> -user <u> -pass <p>
Flags: -interval (default 5m), -timeout (default 10s).
Fires immediately on start, then on interval.
iCal parser supports UTC and local DTSTART/DTEND, skips all-day events.