-
v0.9.0
Stablereleased this
2026-08-09 22:10:26 -05:00 | 91 commits to main since this releaseRepeating events. Creating them, and changing or deleting this occurrence, this and
every later one, or all of them — including the this-and-following split that had
been held back for 0.9.1.Added
-
create_eventcan repeat, from a constrained vocabulary rather than a rawRRULE:
repeat(daily, weekly, monthly, yearly),repeat_interval,repeat_daysfor a weekly
rule, and eitherrepeat_countorrepeat_until. Give neither and it repeats
indefinitely, which is the right answer for a standing weekly meeting.Not a raw rule string, deliberately.
FREQ=SECONDLYwith noCOUNTis legal
iCalendar, expands to millions of occurrences, and turns every later read of that
calendar into a timeout. A vocabulary that cannot express it beats a validator that has
to catch it. -
scopeonupdate_eventanddelete_event—this,following, orall.
Required for a repeating event, with no default. "Cancel Tuesday's standup" means
one occurrence to a person; under a default ofallit ends the standup permanently.
There is no safe default, only a convenient one, so a missing scope is refused with the
three choices named andscope_required: truein the envelope. A scope on a one-off
event is accepted and ignored — it has one occurrence, so every scope names the same
write. -
Every occurrence of a series now carries an
event_id. Before this, repeating
events had none, because every write against them was refused. The handle gained a
fourth field naming which occurrence it is — the value aRECURRENCE-IDcarries, in
UTC so it survives the server'sTZchanging between being issued and being used.
Shorter handles still decode, so a one-off event's handle is byte-identical to the one
0.8.1 issued.What the absence of a handle means has narrowed accordingly: a subscription, or a
calendar that is readable but not writable.
The three scopes are three different operations
Not three flags on one operation. Removing one occurrence of a series is a write, not a
deletion — and that is the whole safety argument here:Scope Update Delete thisadd a RECURRENCE-IDoverride to the same resourceadd an EXDATEto the masterfollowingtruncate the original with UNTIL, create a new resource for the remainderset UNTIL— one write, no second resourcealledit the master DELETEthe resourceOnly
all, andfollowingfrom the very first occurrence, are aDELETE. An
implementation that reaches forobj.delete()because the caller said "delete" takes the
entire series and gets exactly the same 204 back as taking one occurrence, so nothing
downstream can tell.Two more consequences worth stating:
- An override is built by copying the master, properties and VALARM reminders and all,
never composed from the fields this server models. An occurrence becoming an override is
the one moment a reminder could silently disappear, and a lost reminder is invisible in
every response until an appointment does not ring. followingfrom the first occurrence, and truncation generally, degenerate. An
UNTILearlier thanDTSTARTwrites a rule that generates nothing — a resource that
exists and renders as no events at all. That case is detected and treated as the whole
series instead.
The split creates before it truncates
Editing
followingis the only operation here that writes two resources, and CalDAV
offers no transaction across them. One of the two writes can therefore succeed alone, and
the order decides which failure the user gets:- remainder first, then truncate — a failure between them leaves a visible duplicate
series from the split date onward, recoverable by hand; - truncate first — a failure leaves every future occurrence silently gone, behind a
plausible-looking success.
So the remainder is created first, and if the truncation then fails the error says
precisely that both are on the calendar and nothing was lost, rather than "nothing was
modified", which would be a lie.A series bounded by
COUNTrefuses afollowingedit. Splitting it correctly means
knowing how many occurrences fall before the split, which needs the series expanded — and
a count wrong by one is a missing or extra occurrence months away with nothing to indicate
it. Deletingfollowingon a counted series is fine: every occurrence being kept was
inside the original count either way.Changed
-
recurringis read from the resource, not from the component. ARECURRENCE-ID
override carries noRRULE, so asking the expanded copy reported a moved occurrence of
a weekly meeting as a one-off. All of a series' components live in one resource (RFC
4791 §4.1), so asking the resource cannot get it wrong. The flag is now correct for an
override rather than merely not trusted. -
The write instructions and both tool descriptions now carry the scope and its lack of a
default. A model told only that series are "supported" picks a scope itself.
Testing
417 tests, and a twelve-mutation pass over the safety properties — each one
individually reverted, each one caught. The three that were not caught on the first run
became tests before this shipped, and all three were real gaps: a reminder surviving an
occurrence becoming an override, an override not inheriting the series rule, and a second
edit of the same occurrence editing that override rather than adding another.Recurrence has not run against the live account.
docs/plan.mdcarries the eight
checks, which want a scratch calendar — one of them deliberately tries to take a whole
series.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-