Skip to content

Latest commit

 

History

History
665 lines (549 loc) · 43.7 KB

File metadata and controls

665 lines (549 loc) · 43.7 KB

Roadmap: caldav-server-tester

This document is a draft - it was generated by Claude Opus 5.0 on 2026-08-26 from the open issues, the existing documentation, the code and the caldav library's feature database. Rewriting it by the human is work-in-progress. In any case, an estimate is an estimate - the only way to know for sure how much time a task will take is to actually do it.

Funding

NLnet foundation logo    NGI Zero Core Logo

This project is funded through the NGI Zero Core fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology.

The funded scope is 80 hours, plus a separately budgeted 20 hours of documentation work carried out by another person. The documentation hours are not counted inside the 80 and are not planned in this document; the phases below are the 80.

The v1.3.0 release itself is included in those 80 hours. It was originally intended to be done before the funding period, but it's been dragging on - reviews, review fixes, running the checks and understanding how things break can take a lot of time. 8 hours was budgeted for the v1.3.0-release + server testing, in reality several working days was spent.

How to read this document

Unlike the sibling projects, the GitHub issue tracker is not yet the source of truth here. This repository has exactly one open issue, #3.

Every item names a deliverable and has a status: [x] done, [ ] wip partly done in the v1.3.0 cycle (the text around the table says what is left), [ ] not started. The hours are the estimates as made on 2026-08-26, they have not been re-estimated.


Overview

caldav-server-tester probes a CalDAV server and reports which parts of RFC 4791 (and the surrounding RFCs) it supports, which it handles quirkily, and which it gets wrong. Its output is a feature profile in the exact shape that the caldav client library consumes, so a run against a new server produces a ready-made compatibility profile.

That makes the tool structurally unusual, and the constraint drives most of this roadmap: the feature definitions live in the caldav library, not here. caldav/compatibility_hints.py holds 125 feature entries; this repository holds 36 check classes that fill them in. A probe for a feature that has no entry in the library warns and records nothing, so essentially every item below lands as two commits in two repositories, and the two releases go out in lock-step (as CHANGELOG.md already states).

The second thing that shapes the plan: the tool's verdicts are not a report that somebody reads and forgets. They are copied into the library's server profiles, where they switch real client behaviour on and off. A probe that reports full for a feature the server does not have is worse than no probe at all - and the v1.3.0 development cycle found several of exactly that kind, all of them in probes that had looked fine for a year.

Baseline: v1.2.0

The baseline is v1.2.0, released 2026-04-24 against caldav 3.2.1.

A lot of work went into v1.3.0 - a hundred commits, and a long CHANGELOG-entry.

v1.2.0 (baseline) v1.3.0
Check classes 23 36
Feature entries defined in compatibility_hints.py 94 (85 of them server-feature) 125 (116)
Leaf server-features, i.e. actually probeable 53 76
...of those, probed by this tool 44 68
...defined but never probed 9 8
Legacy incompatibility_description flags not yet migrated 16 9
Server profiles in the library 20 22 (Bedework split in two, Infomaniak added)
Unit tests 98 521
Coverage, total (checks.py) 39% (34%) 70% (68%)
Lines in checks.py 2,846 6,651

Coverage is coverage.py's combined statement and branch figure, as configured in pyproject.toml; v1.2.0's tests were run against caldav 3.2.1.

Covered by v1.3.0, and not to be re-estimated as new work: calendar creation/deletion and the display-name and stable-URL behaviour around it; save/load of events, tasks and journals including mixed-component calendars; time-range search across all three component types, with open-ended ranges, DURATION semantics and sliding-window detection; text search with case sensitivity and substring matching; is-not-defined searches; recurrence search, both server-expanded and implicit, including the SEQUENCE-on-exception case; alarm search; sync-collection (RFC 6578); principal discovery (RFC 5397) and .well-known bootstrapping; duplicate-UID handling; timezone handling; RELATED-TO; mutability and If-Match; and a substantial part of RFC 6638 scheduling - six check classes covering inbox delivery, schedule-tag, auto-scheduling, the calendar-user-address-set and the scheduling free-busy query.

The v1.3.0 cycle also added the things that make the next verdicts trustworthy: fixtures moved out of year 2000 into a sliding window that servers do not hide, data-loss protection when --caldav-calendar names a real calendar, a probe-calendar purge, a --cleanup-only flag, a lifecycle that always reports and always cleans up, and the url.encode-at probe. September added the measured synchronous-write, CheckPutEtag, the empty-207 and save-load.event.no-summary probes, and create-calendar.with-supported-component-types.

Guiding constraints

  1. Every item lands in two repositories. The feature definition belongs in caldav/compatibility_hints.py, the probe belongs here, and a probe without its definition silently records nothing. Estimates below include the library side of the change; they do not include library work that would be needed for its own sake (see the Risks section on where the boundary sits).

    That is a dependency, not an order of work. Nothing is put in stone on the library side first: an item is developed on corresponding branches in both repositories at once, because probing real servers is what reveals that a feature was defined wrongly, and the per-server feature matrix can only be filled in once the probe exists and has been run. The order applies to the releases, not to the commits - caldav first, then the tool.

  2. A wrong verdict is worse than no verdict. unknown is a legitimate, deliverable result. Several fixes in v1.3.0 exist because a probe guessed where it should have abstained - delete-calendar.free-namespace inferred from a previous run's leftovers, get-current-user-principal turning a transient 503 into unsupported, search.comp-type.optional comparing against a counter that could never match. New probes are written to abstain, and the audit in item 1.3 is about the old ones.

  3. Probes must be safe to run. The stated goal is that the tool can be pointed at a personal calendar without eating it. Two paths that could have done exactly that were fixed in v1.3.0; item 1.2 finishes the job or documents honestly that it cannot be finished.

  4. Coverage of the RFC is not the goal. Coverage of what servers get wrong is. A probe for a feature that all 22 servers implement correctly is worth less than a probe that separates them, and much less than one that catches a server silently discarding data. Where an RFC turns out to be uniformly supported or uniformly absent, the deliverable is the finding, and the remaining hours move.

  5. Lock-step releases. The tool is released alongside the caldav library because the feature database it depends on ships there and may change in a patch release. Each phase is scoped to end in a release pair.


Phase 1: Trustworthy verdicts (14 hours)

The tool's output configures a client library. Before adding more of it, the existing output has to be known-good. This phase buys that, and it is first because everything in Phases 2-4 inherits the infrastructure it fixes.

Status Item Deliverable h Issue
[x] 1.0 Release v1.3.0 The [Unreleased] changelog section closed, the release cut and published to PyPI 4
[x] 1.1 Re-run the fleet, correct the profiles A full run against every reachable server in the 22-profile fleet on the v1.3.0 code, with each disagreement between the run and the stored profile resolved as either a corrected profile entry or a fixed probe 4
[ ] wip 1.2 Safe against a production calendar Either a documented guarantee of what the tool will and will not delete when --caldav-calendar names a real calendar, backed by tests, or a documented verdict that the guarantee cannot be made and a loud refusal to run without an explicit override 4
[ ] wip 1.3 Feature taxonomy and the lookup.* rename docs/TODO.md's rename carried out across both repositories in one landing: non-existing-raises-not-foundlookup.not-found, the lookup.get-by-url question decided either way, and the url.* / lookup.* split stated in the feature descriptions 2

Status after v1.3.0. 1.0 and 1.1 are done: the release is cut, and the checker has been run against every reachable server in the fleet except Posteo, where the account has to be funded before more testing, with the profile corrections landed in the caldav library. 1.2 is half done. The code now refuses to delete a calendar the user named, only ever deletes objects whose UID starts with csc_, spares user calendars in the probe-calendar sweep and restores calendar-color and calendar-order after probing them, and unit tests pin all of that. What is missing is the documented guarantee: USAGE.md still only says that running against a personal calendar "should be safe". 1.3 has groundwork only: non-existing-raises-not-found.collection was split out, but nothing has been renamed.

The budget gives 1.0 and 1.1 four hours each. Both took far longer - several working days between them - because every profile correction is a small piece of evidence that a probe was wrong, and the v1.3.0 cycle's hit rate on that was high: probing Robur on a single day produced two probe fixes and a changed verdict. A deep investigation into any one server is charged against the hours its RFC sits in.

1.2 carries the maintainer's own note from the June 2026 code review: "The original goal was to make the tester in such a way that it would be possible to run it on a production calendar (...) I'm not sure if this goal is achievable, but it's worth investigating - and if it is too difficult, then at least the documentation should be very clear on it." Both outcomes are accepted deliverables; what is not accepted is leaving it ambiguous.


Phase 2: Close the gap between defined and probed (12 hours)

Two backlogs of the same shape, both of them bounded and countable: features the library declares but nothing measures, and quirks that predate the feature database entirely and still live in the old flat flag list.

At the baseline nine features were defined in compatibility_hints.py and probed by nothing. Eight still are: get-supported-components, save-load.reuse-deleted-uid, save-load.todo.recurrences.thisandfuture, search.time-range.accurate, search.time-range.journal, search.text.category.substring, wrong-password-check and http.multiplexing. Every one of them is currently reported from its default rather than from evidence - which is exactly the failure mode constraint 2 is about, at eight known sites. The ninth, create-calendar.with-supported-component-types, got its probe on 2026-09-13, after Bedework 5 turned out to ignore the component set - the first server where the default was wrong.

At the baseline sixteen legacy flags were left in incompatibility_description. The v1.3.0 cycle retired seven of them. calendar_color, calendar_order, duplicates_not_allowed, non_existing_raises_other, propfind_allprop_failure and vtodo_datesearch_nodtstart_task_is_skipped got probes. no_overwrite already had one in v1.2.0 and was removed from the library afterwards. Nine remain, all nine still read by the caldav library's own test suite: dav_not_supported, event_by_url_is_broken, fastmail_buggy_noexpand_date_search, robur_rrule_freq_yearly_expands_monthly, sticky_events, vtodo-cannot-be-uncompleted, vtodo_no_due_infinite_duration, vtodo_datesearch_notime_task_is_skipped and vtodo_datesearch_nodtstart_task_is_skipped_in_closed_date_range. Migrating them is issue #3, which is the continuation of caldav#532.

Status Item Deliverable h Issue
[ ] wip 2.1 Probes for the legacy flags A FEATURES entry and a probe for each incompatibility_description flag (sixteen at the baseline, nine left after v1.3.0), with the server profiles filled in from real runs rather than carried over 6 #3
[ ] wip 2.2 Probes for the defined-but-unprobed features Nine probes; any feature that turns out to be unprobeable in practice (http.multiplexing needs a specific reverse-proxy setup) documented as such in its description rather than left looking measured 4
[ ] 2.3 Retire _old_flags The _old_flags shim gone from the server profiles and from the caldav test suite, once 2.1 has replaced its readers 2 #3

http.multiplexing deserves a warning: it is a deployment property (an nginx in front of Baikal), not a server-software property, so it may not belong in a per-server profile at all. Deciding that is part of 2.2 and the answer may be "delete the feature".

2.3 is small only because 2.1 does the work; it is listed separately because it is the item that actually closes issue #3, and because it lands entirely in the other repository.


Phase 3: Protocol coverage - the unprobed RFCs (30 hours)

This is the original plan, re-estimated against what the baseline already contains. The ordering is by expected yield: the RFCs where servers are most likely to differ from each other come first.

Status Item Deliverable h Issue
[ ] 3.1 RFC 6638 - complete the scheduling probes Probes for the parts of CalDAV scheduling the six existing check classes do not reach: iTIP REPLY and COUNTER round-trips, schedule-default-calendar-URL, the schedule-* privileges, and cancellation 6
[ ] wip 3.2 ETags and conditional requests Probes for etag stability across reads, etag change on write, weak vs strong etags, If-None-Match on GET and on PUT-as-create, and collection etags 5
[ ] 3.3 RFC 3744 - WebDAV ACL Probes for DAV:acl and current-user-privilege-set reporting, and for whether a granted privilege is actually enforced 5
[ ] 3.4 draft-pot-caldav-sharing-01 Probes for calendar sharing: invite, reply, the shared-calendar properties, and what a sharee actually sees 4
[ ] 3.5 RFC 7986 - new iCalendar properties Probes for whether COLOR, IMAGE, CONFERENCE and the new parameters survive a save/load round-trip on a VEVENT, with control probes for an X- property and an unregistered one; research on what happens to the calendar-level properties (NAME, SOURCE, REFRESH-INTERVAL, UID, ...) on the VCALENDAR wrapper, and whatever keys that justifies; and the VCALENDAR-UID-as-identity peculiarity behind caldav 7326ba2. Fleshed out below 3
[ ] 3.6 RFC 7953 - VAVAILABILITY Probes for storing a VAVAILABILITY component and for whether the server honours it in a free-busy query 3
[ ] 3.7 RFC 5689, RFC 8607, RFC 4331 Extended MKCOL (create a calendar with properties in one request), managed attachments (POST with add-member, attachment lifecycle), and a research-only verdict on quota reporting 4

3.1 is 6 hours, not the 12 the original list carried, because scheduling is the one RFC on this list that is already half-built. CheckScheduling, CheckSchedulingDetails, CheckSchedulingInboxDelivery, CheckScheduleTag, CheckScheduleTagStablePartstat and CheckFreeBusyQueryRFC6638 exist, the multi-account configuration they need exists, and the seven scheduling.* features they fill are already in the database. What is left is genuinely incremental.

3.2 is about half done in v1.3.0: CheckIfMatchOptional probes whether the server enforces If-Match. CheckPutEtag reads the ETag a PUT answers with (quoted or percent-encoded, weak or strong) and replays it in If-Match. It also tries If-Match: * and If-None-Match: * on an existing and a missing object, which covers If-None-Match on PUT-as-create. Left: whether the etag stays the same across reads and changes on a write, If-None-Match on GET, and collection etags.

3.3, 3.4 and 3.7 share a risk that 3.1 and 3.2 do not: they may have no positive case to verify against. A probe for a feature that none of the 22 servers implements cannot be distinguished from a broken probe. Where that happens the deliverable is the negative finding - documented, with the probe left in place and reporting unknown rather than unsupported - and the unspent hours move to 1.1 or to Phase 4. Managed attachments (3.7) is the most likely candidate; ACL (3.3) the least, since ACL predates CalDAV and several servers ship it.

3.5 in detail: RFC 7986

This is the one Phase 3 item that has been through a planning round. The notes below record what was decided and what still has to be researched, so the three hours are not spent rediscovering it.

What the RFC defines. New properties: NAME (VCALENDAR, multiple, one per LANGUAGE), REFRESH-INTERVAL (VCALENDAR, VALUE=DURATION), SOURCE (VCALENDAR, VALUE=URI), COLOR (VCALENDAR and the components, a CSS3 colour name), IMAGE (VCALENDAR and the components, VALUE=URI or VALUE=BINARY, media type image/*, multiple), CONFERENCE (VEVENT and VTODO only, VALUE=URI, multiple). Properties extended to VCALENDAR: DESCRIPTION, UID, LAST-MODIFIED, URL, CATEGORIES. New parameters: DISPLAY on IMAGE (BADGE - the default - GRAPHIC, FULLSIZE, THUMBNAIL), EMAIL on ORGANIZER and ATTENDEE, FEATURE on CONFERENCE (AUDIO, CHAT, FEED, MODERATOR, PHONE, SCREEN, VIDEO), LABEL on CONFERENCE and IMAGE.

What already exists. CheckCalendarProperties probes calendar-color, calendar-color.hex and calendar-order - the Apple/Mozilla WebDAV collection properties, which are not RFC 7986 and are no longer part of this item. CheckRelatedTo is the template every probe below follows: PUT a hand-written VCALENDAR, load it, count what came back, grade it, delete in a finally.

Feature naming. Optimise for leaving the maximum amount of configuration implicit: a hand-maintained per-server list of which iCalendar properties a server keeps is exactly what should not be built. Almost every server will round-trip either all or none of the new component-level properties, while the VCALENDAR level is a genuinely separate question - so the split is by where the property sits, not by which property it is:

Key Meaning Default
save-load.event.rfc7986 the RFC 7986 properties and parameters survive a round-trip on a VEVENT full
save-load.event.rfc7986.image.binary split out only if a real server keeps some and drops others -
save-load.event.x-property an X-prefixed property and an X- parameter survive full
save-load.event.any-property a non-X- unregistered property survives (invalid iCalendar; a server is within its rights to drop it) unknown
save-load.vcalendar grouping node; full, not probed - it is not clear what unsupported would even mean full
save-load.vcalendar.rfc7986 the calendar-level properties survive a round-trip on the wrapper see below
save-load.vcalendar.x-property / .any-property the same two control probes at the wrapper level as above

X- properties are standard (RFC 5545 §3.8.8.2): a server dropping one is breaking a rule, which is why that key defaults to full and a bare CSC-PROBE: does not. Both are worth having, and both are worth having at both levels - a server may parse the wrapper and pass the component through verbatim, or the reverse. VTODO, VJOURNAL, VALARM and VTIMEZONE are deliberately not probed; the component-level answer is not expected to vary by component type, and if it does, that is a finding for a later item rather than eight more keys.

This breaks with the existing save-load.icalendar.related-to, which is an RFC 5545 component property sitting under an icalendar node. The rename to save-load.event.related-to should ride with item 1.3, which is already doing a coordinated cross-repository rename and knows how to land one.

Defaults. RFC 7986 is an established standard, so full is the right default for its keys - the argument for fragile on calendar-color was that a non-standard extension should not clutter the matrix, and it does not apply here. save-load.event.any-property is the oddball and defaults to unknown. Note that unknown reads as False through is_supported(..., bool), so a skip-style gate in the caldav test suite abstains correctly - but a test that asserts the negative branch would not. Verify that before relying on it.

Probe design.

  • Grade generously on formatting. COLOR:BLACK coming back as black, Black or #000000 is full; the tool is measuring whether the datum survived, not how it was spelled. Use a non-default DISPLAY value so a dropped default is never mistaken for a dropped parameter.
  • IMAGE;VALUE=BINARY gets an 8x8 image, not a 1x1. A server whose limit excludes an 8x8 GIF does not meaningfully support inline images, and probing max-resource-size (RFC 4791 §5.2.5) properly is out of scope here - which also means a failure at this size should be reported as such rather than silently graded as "no IMAGE support".
  • Read the object back both by GET and by REPORT. A server that stores the bytes verbatim can still regenerate calendar-data inside a calendar-multiget/calendar-query response from a parsed model and drop what it does not understand only there - and a client that finds its events by searching would never see what it saved. A divergence is broken with a behaviour note.
  • If the property survives but its parameters do not, that is fragile with a note, and a candidate for splitting a subkey out later - not a reason to define the subkeys up front.

The VCALENDAR level: research before probes. A VCALENDAR in CalDAV is a recurrence set, not a calendar, so the calendar-level properties are being carried by something they were not designed for. Four questions, in the order they should be answered against the docker test servers:

  1. Does any server populate these fields in what it returns - a NAME or COLOR synthesised from the collection's own properties?
  2. Do they survive a PUT round-trip?
  3. Is there a mapping between the iCalendar properties and the WebDAV collection properties - NAMEDAV:displayname, DESCRIPTIONCALDAV:calendar-description, COLOR ↔ the Apple calendar-color, UID ↔ the cal_id? No RFC defines such a mapping, but the de-facto set exists: for subscribed calendars sabre/dav (and therefore Baikal) stores exactly source, displayname, refreshrate, calendarorder and calendarcolor side by side - {http://calendarserver.org/ns/}source and refreshrate being the WebDAV analogues of RFC 7986's SOURCE and REFRESH-INTERVAL. Whether any server bridges the two representations is worth an hour of looking, and the answer is a paragraph in the report either way.
  4. RFC 4791 §9.7 roots comp-filter at VCALENDAR, so a prop-filter on a calendar-level property is expressible. If the properties survive or are populated, filtering on them can be probed - but that lands in item 4.3, not here.

Only after 1-3 is it clear how many keys save-load.vcalendar.rfc7986 needs and what its default should be. As many as the evidence demands, and no more.

The VCALENDAR UID trap. caldav commit 7326ba2 ("fix: no random UID on the VCALENDAR wrapper") is the most valuable single input to this item, and it is not a round-trip question. icalendar.Calendar.new() adds an RFC 7986 calendar-level UID; Stalwart takes that UID to be the identity of the calendar object resource, so a second save of the same object arrived carrying a fresh UID and was rejected with 412 no-uid-conflict. The library now builds the wrapper without one, which makes this a latent trap rather than a live bug - invisible to every existing probe and to every test, waiting for the next caller that builds its own wrapper. It needs research before it needs a feature key:

  • Where does the server store that UID, and which of the two UIDs does it report back?
  • Can one calendar hold two objects whose component UIDs differ but whose wrapper UIDs collide, or the reverse?
  • Is Stalwart reading the first UID it meets in the stream, or specifically the calendar-level one?

Then a server-peculiarity key - save.vcalendar-uid-is-identity or similar - so the library can be told rather than having to remember. Stalwart is in the caldav repository's tests/docker-test-servers/, so this is reproducible locally.

CATEGORIES, answered. Multiple CATEGORIES lines in a VEVENT were already legal under RFC 5545 §3.6.1, which lists categories among the properties that "MAY occur more than once"; the comma-separated form in §3.8.1.2 is an additional convenience, not a restriction. What RFC 7986 §5.6 added is CATEGORIES at the VCALENDAR level, where multiple properties are unioned. The interesting question that remains is whether a server's text-match finds a category that sits on the second line, and that belongs to search.text.category in item 4.3.

Other RFCs introducing calendar properties, for the record: RFC 4791 §5.2 (calendar-description, calendar-timezone, supported-calendar-component-set, supported-calendar-data, max-resource-size, the min/max date-time and instance limits), RFC 6638 (the schedule-* properties), RFC 6578 (sync-token), RFC 4331 (quota), RFC 5397 (current-user-principal), RFC 3744 (ACL), RFC 8607 (managed-attachments-server-URL), RFC 7809 (CALDAV:calendar-timezone-id, CALDAV:timezone-service-set), and draft-pot-caldav-sharing-01 (the sharing properties). Non-standard but widely implemented: {http://apple.com/ns/ical/}calendar-color and calendar-order, and the {http://calendarserver.org/ns/} set (getctag, source, refreshrate, subscribed-strip-*). Most of these already have their own roadmap item; none of them defines an iCalendar-to-WebDAV mapping.

What the three hours buy. The component-level probe with its two control probes, the GET-vs-REPORT axis, the VCALENDAR-level research and whatever keys it justifies, and the UID peculiarity. Deliberately deferred: filtering on calendar-level properties (item 4.3), per-property subkeys (only on evidence), and the full iCalendar-to-WebDAV mapping survey - which may well turn out to be worth more than the round-trip probes, and would then be raised as its own item rather than squeezed in here.


Phase 4: Search semantics (10 hours)

Search is where CalDAV servers differ most, where the caldav library carries the most workaround code, and where the existing probes are broadest - but they are broad rather than deep. These three items go deeper on the parts of RFC 4791 §9.7 that nothing currently touches.

Status Item Deliverable h Issue
[ ] 4.1 Negated searches Probes for negate-condition on text-match (RFC 4791 §9.7.5): whether the server honours it, ignores it, or - the dangerous case - returns the un-negated result set 4
[ ] 4.2 Collations Probes for the collations RFC 4791 §7.5 makes mandatory (i;ascii-casemap, i;octet), for what a server does with an unknown collation (CALDAV:supported-collation should give a precondition error, not silence), and for the supported-collation-set property 4
[ ] 4.3 prop-filter and param-filter depth Probes for parameter filtering and for multi-condition prop-filter composition, closing out search.text.category.substring from 2.2 2

4.2 is a deepening, not a new area: CheckCaseSensitiveSearch already drives i;octet and i;ascii-casemap to decide the two search.text.case-* features. The feature description in the library says so explicitly - "a text-match may pass a collation, and i;ascii-casemap MUST be the default, this is not checked (yet - TODO)" - and that TODO is what this item closes.

4.1 is the item with the highest chance of finding a real, damaging bug. A server that silently ignores negate-condition returns the complement of what the client asked for, and no client-side sanity check would catch it.


Phase 5: Quality assurance of the checker itself (14 hours)

At the v1.2.0 baseline the tool had 98 tests and 39% coverage, with checks.py - holding essentially all of the logic - at 34%. v1.3.0 has 521 tests and 70% coverage, and checks.py has grown to 6,651 lines at 68%, so 5.1 is close, but no coverage floor is enforced anywhere yet. Every correctness bug in the June 2026 review lived in code that thin on tests, and the failure mode is specific to this project: an untested probe does not crash, it reports a wrong verdict, which is then copied into a server profile and shipped as client behaviour.

Status Item Deliverable h Issue
[ ] wip 5.1 Coverage for checks.py checks.py at or above 70% statement coverage, with a coverage floor enforced in CI so it cannot drift back 6 #3
[ ] 5.2 Type annotations and static checking Annotated public functions (18 of 59 functions carried any annotation at the baseline, 55 of 186 in v1.3.0), mypy running clean in CI 4
[ ] 5.3 Continuous regression runs A CI job that runs the full checker against locally started Xandikos and Radicale instances and fails when the reported feature set changes unexpectedly 4

5.3 is the item that would have caught the two probe bugs 1.1 is budgeted to find by hand, and it is what keeps that hand-work from having to be repeated every release. It is deliberately last: it locks in a feature set, so it is worth locking in only once Phases 1-4 have settled what that set should be.


Effort summary

Per-item hours are in the phase tables above.

Phase Hours Share
1 - Trustworthy verdicts 14 17.5%
2 - Close the gap between defined and probed 12 15%
3 - Protocol coverage: the unprobed RFCs 30 37.5%
4 - Search semantics 10 12.5%
5 - Quality assurance of the checker itself 14 17.5%
Total 80

Separately budgeted and not counted above: 20 hours of documentation work, carried out by another person.

Changes from the original list

The original plan was twelve lines, all of them "add probes for RFC N", summing to exactly the 80 funded hours. It carried its own note - "expand the list below" - and the expansion turned out to be a reprojection: 42 of the 80 hours now sit in work the flat list did not name.

Original item Was Now Why
RFC 6638 12 6 Half-built already. Six check classes and seven scheduling.* features exist, and so does the multi-account test configuration they need. What remains is iTIP REPLY/COUNTER, the schedule privileges and cancellation
etags 8 5 CheckIfMatchOptional already covers conditional writes; what is left is the etag's own behaviour
draft-pot-caldav-sharing-01 8 4 Unchanged in ambition, but re-estimated as one probe family rather than a subsystem - and discounted for the real chance that no reachable server implements the draft, in which case the deliverable is the finding
RFC 3744 5 5 Unchanged
RFC 7953 5 3 Two probes (store a VAVAILABILITY; see whether free-busy honours it), on top of free-busy infrastructure that already exists
RFC 7986 4 3 Mostly a save/load round-trip over properties, on fixtures that already exist. The calendar-color/calendar-order half of the original estimate is gone - those are Apple WebDAV collection properties rather than RFC 7986, and CheckCalendarProperties already probes them. What replaced it is the VCALENDAR level: research first, keys afterwards (see 3.5 in detail)
RFC 5689 4 4 Merged with RFC 8607 and RFC 4331 into one line for the three smaller extensions
RFC 8607 4 "
RFC 4331 (research only) 2 "
Negated searches 4 4 Unchanged
More work on collations 4 4 Unchanged
Various other research and additional checks 20 0 Distributed, and named. This is where the 42 hours came from: the twenty unspecified hours plus the twenty-two freed by re-estimating the half-built items above
(new) Release v1.3.0 (1.0) - 4 Not in the original list, and for a while not in this document either: the release was going to be donated to the grant. It is now charged at four hours, next to four for re-running the fleet (1.1)
(new) Phase 1 - trustworthy verdicts (1.1-1.3) - 10 The tool's verdicts configure a client library. The v1.3.0 cycle found several probes reporting full for features the server did not have, all of them a year old. Auditing that is worth more than another RFC
(new) Phase 2 - defined but unprobed - 12 Nine features at the baseline were declared in the database and measured by nothing; sixteen legacy flags predate the database entirely. Both backlogs are countable, and one of them is the only open issue in the tracker
(new) Phase 4 - prop-filter/param-filter depth (4.3) - 2 Parameter filtering and multi-condition prop-filter composition. Not in the original list: the two search lines it sits beside are the ones that were named, and this is the part of RFC 4791 §9.7 neither of them reaches
(new) Phase 5 - QA of the checker - 14 34% coverage at the baseline on the module that holds all the logic, and 18 of 59 functions with any type annotation. An untested probe does not crash - it ships a wrong verdict

Risks

Every deliverable needs a server that exhibits the behaviour

This is the structural risk of the whole project, and Phase 3 concentrates it. A probe can only be validated against a server that implements the feature and a server that does not; without both, a green result is indistinguishable from a probe that always says yes. The v1.3.0 cycle has a worked example of the failure mode: search.comp-type.optional compared its result against a counter it could never reach, and confidently mislabelled seven correct servers.

For ACL (3.3) the fleet almost certainly provides both cases. For managed attachments (3.7) and the sharing draft (3.4) it may provide neither.

Mitigation: the deliverable for each Phase 3 item is defined to include the negative outcome - a documented finding that no reachable server implements the RFC, with the probe reporting unknown rather than a guess. Hours freed that way move to 1.1, which can absorb any amount of work.

Access to servers, and the cost of probing them

The 22 profiles include hosted services the author holds an account on. Accounts lapse, providers change, and some of the more invasive probes (ACL, sharing, attachments) need a second account on the same service, which several of them cannot provide. Probing aggressively also risks rate-limiting or a suspended account, which loses the profile for good.

Mitigation: self-hostable servers (Xandikos, Radicale, Baikal, Davical, Cyrus, Bedework, Robur, Stalwart) carry most of the fleet and cost nothing but time, CPU-power and electricity; item 5.3 makes two of them permanent CI infrastructure. Where a hosted service is the only witness for a behaviour, that fact is recorded in the profile so a future maintainer knows the evidence is not reproducible.

The two-repository split

Nothing in this roadmap can ship from this repository alone. Every feature needs its definition in caldav/compatibility_hints.py, the two releases go out in lock-step - caldav first - and two items (1.3 and 2.3) are outright broken if the halves land out of order. The development itself happens on corresponding branches in both repositories at once; see guiding constraint 1.

There is also a budget boundary here. compatibility_hints.py has known problems of its own - it carries a TODO saying it should be split into three files, another saying the definition database should perhaps move out of Python entirely, and a review document (caldav/docs/design/TODO_COMPATIBILITY_HINTS.md) listing an inverted boolean branch and a redundant derivation call. None of that is funded here. This roadmap budgets only the per-feature library edits its own probes require.

Mitigation: state the boundary, as above, rather than discovering it halfway through Phase 2. If the library-side cleanup turns out to block a probe, the blocking part is raised against the caldav grant rather than absorbed silently.

A wrong verdict is worse than no verdict, and this project ships verdicts

The tool's output does not stop at a report. It is copied into server profiles, where the caldav library switches workarounds on and off according to it. A false full disables a workaround a user needs; a false unsupported disables a feature that works. Both are silent.

Mitigation: constraint 2 above, item 1.1 as a one-off sweep, and item 5.3 as the standing guard. It is also why Phase 5 is inside the funded scope rather than deferred: the coverage number on checks.py is a proxy for how many probes have ever been exercised against a known answer.

The QA phases have no visible output

Twenty-six hours across Phases 1 and 5 produce nothing a user can point at. They are the first thing to be cut when time runs short, and they are what the other 54 hours rest on.

Mitigation: they are scheduled first and last rather than in the middle, so neither can be quietly dropped; their deliverables are numeric (a coverage floor, mypy clean, a corrected profile count) rather than subjective; and CI enforcement means the gains cannot erode afterwards.

Maintainer bandwidth

The primary maintainer maintains several packages, several of them NLnet-funded, alongside other priorities in life. It is a real risk that time runs out before the funds do.

Mitigation: the five phases are independent of one another - Phase 3 does not need Phase 2, and Phase 4 does not need Phase 3 - so partial delivery is meaningful in any order. Phase 1 alone leaves the library's users better off than today. There are other developers in the pycal pool who may pick up pieces.


Beyond the funded scope

Everything below is within the project's ambition but outside the 80 funded hours. The maintainer intends to continue developing the tool with or without funding, though the first priority will always be to get food on the table.

Item Rough estimate Notes
A published, continuously updated compatibility matrix 20-40 h The ambition behind issue #2: today the results are a Python dict in a library. A browsable per-server, per-feature matrix would be the single most useful output this project could produce for people who are choosing a CalDAV server
Restructuring compatibility_hints.py 8-16 h Its own TODO: split feature definitions, the server database and the lookup logic into three, and consider moving the definitions out of Python into a data format. Library-side work, and it would have to be funded there
JMAP calendar probing 20-30 h The caldav library has grown a jmap/ module. The same "what does this server actually do" question applies to JMAP (RFC 8620/8984), and the check infrastructure would largely carry over
A CardDAV server tester 30-50 h RFC 6352 is CalDAV's sibling and has the same interoperability problem, with no equivalent tool
RFC 7808 / 7809 - timezone service and timezones by reference 8-16 h Whether a server offers a timezone service, and what it does with TZID references it does not know
RFC 9073 / RFC 9074 - event publishing and VALARM extensions 8-12 h Newer iCalendar extensions; likely thin server support today, which is itself worth documenting
Rate-limit and performance profiling 8-12 h rate-limit is defined in the database as a client-feature but is never measured. Also: how a server degrades on large calendars, which is where several of them fall over in practice
Async / aio probing 8-12 h The caldav library has an async client; nothing checks whether servers behave the same under concurrent requests
A reproducible server fleet (containers) 12-20 h A compose file bringing up every self-hostable server in the fleet at pinned versions, so a run is reproducible and a regression can be bisected across server versions
Automated upstream bug reports 8-16 h The tool finds real server bugs. Turning a finding into a filed, reproducible report against the server project is currently entirely manual

References

Standards

Related projects

  • python-caldav - same author; this tool's backend, and the home of the feature database it fills in
  • plann - same author; a CalDAV CLI, separately NLnet-funded
  • calendaring-client - same author, separately NLnet-funded; a multi-protocol calendaring library

Internal documents

  • README.md - what the tool is
  • USAGE.md - all CLI options, output formats, and how to contribute a server profile
  • CHANGELOG.md - what the v1.3.0 cycle did towards this roadmap is in the 1.3.0 section
  • docs/TODO.md - the lookup.* taxonomy discussion behind item 1.3