Recurrence Expansion Parser & Conformance Fixtures #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implement the recurrence expansion engine and conformance test fixtures exercising iCalendar recurrence rules (
RRULE,RDATE,EXDATE, detached overrides, andRANGE=THISANDFUTURE) against CalDAV server expansion output, ensuring recurrence identity preservation and rigorous boundary conformance.Background & Technical Rationale
iCalendar recurrence handling (RFC 5545, RFC 4791 Section 9.6) is notoriously fraught with subtle edge cases, timezone ambiguities, and divergent server implementations. Rather than implementing an ad-hoc, speculative local recurrence engine in Rust, Life establishes bounded server-side CalDAV
REPORTcalendar-data expansion as its primary recurrence engine.However, relying on server-side expansion does not eliminate the need for strict client-side validation and normalization. When Radicale returns a multi-component
VCALENDARstream (containing master series, detachedRECURRENCE-IDcomponents, and exception dates), Life must parse, validate, and normalize these streams into distinct, deterministic occurrence items while maintaining strict architectural invariants:Canonical occurrence identity is
(collection_id, uid, recurrence_id). Therecurrence_idcaptures the original scheduled date/instant and timezone semantics of the recurrence instance. When an occurrence is rescheduled (e.g. moved from April 10 to April 11), itsDTSTARTchanges, but itsRECURRENCE-IDremains20300410T090000Z. The identity is immutable across rescheduling.A query for a half-open window
[from, to)must accurately reflect overrides that move across window boundaries:STATUS:CANCELLED) must be excluded from busy capacity and agenda display.EXDATEentries must be strictly excluded from the expanded set.If a CalDAV server or local parser encounters recurrence edge cases that cannot be expanded losslessly—such as
RANGE=THISANDFUTUREor unmapped customVTIMEZONEdefinitions—the system MUST NOT pretend that the master-only representation is complete. It must return an explicitcalendar_unsupportederror or emit an explicitly partial agenda withincomplete_calendarwarnings. An unsupported temporal window can never be labeled as free capacity.All expansions must enforce half-open
[from, to)bounds. Queries exceeding a 93-day span or generating >10,000 instances must returnlimit_exceededwith instructions to narrow the range, preventing unbounded memory allocation or denial-of-service from infinite recurrence rules.Exact Technical References & Specifications
Normative Recurrence Fixture
From
docs/technical/verification.md(lines 54–81):Normative expansion assertion (
verification.md, line 81):Verification Matrix Scenarios
From
docs/technical/verification.md(lines 39–42):Recurrence & Mutation Contract
From
docs/technical/calendar.md(lines 31–36):Conformance Gates
From
docs/technical/calendar.md(lines 43–53):Section Citations
docs/technical/calendar.md: Section Discovery and conditional writes (lines 7–8), Section Query and time semantics (lines 17–28), Section Recurrence and mutation (lines 31–36), Section Conformance gates (lines 43–53).docs/technical/verification.md: Section Calendar scenarios (lines 39–42, Scenarios C05, C06, C07, C08), Section Representative fictional ICS fixture (lines 52–81).docs/technical/domain-model.md: Section Identity and relationships (lines 44–46).What to Build
1. Multi-Component Recurrence Parser & Normalizer (
src/calendar/recurrence.rs)RecurrenceNormalizerthat ingests raw multi-componentVCALENDARstreams (parsed viaicaloricalendar).VEVENT/VTODOwithoutRECURRENCE-ID) from detached overrides (VEVENT/VTODOwithRECURRENCE-ID).RecurrenceIdrepresentation:UID. If an override arrives without a matching master in the resource stream, handle it gracefully without crashing, logging diagnostic context.docs/technical/calendar.md:7,33). The adapter validates, normalizes, and bounds server-expanded occurrences against master resources and detached overrides; it does not replace the server with a speculative local RRULE generator.(\text{RRULE occurrences} \cup \text{RDATE}) \setminus \text{EXDATE}.RECURRENCE-IDto confirm that the server applied override properties (such as relocatedDTSTART).recurrence_id), preserving the original recurrence value type and timezone context. The first occurrence in the series hasrecurrence_idset to its scheduled DTSTART/DUE instant (2030-04-08T09:00:00Z), distinguishing the occurrence from the unexpanded master series resource (recurrence_id: None).STATUS:CANCELLEDfrom agenda display and capacity reservation.[from, to): include detached overrides moved into[from, to)even if the originalRECURRENCE-IDfell outside; exclude overrides moved outside[from, to)even if the originalRECURRENCE-IDfell inside.RANGE=THISANDFUTUREorRANGE=THISANDPRIOR, and the server expansion does not expand it, returnCalendarError::Unsupported("calendar_unsupported").VTIMEZONEcannot be mapped or resolved to IANA timezone rules, returnCalendarError::Unsupported("calendar_unsupported").2. Query Span & Instance Bounding
to - from > 93\text{ days}, reject immediately withCalendarError::LimitExceeded("Query span exceeds 93 days maximum limit; narrow query range").CalendarError::LimitExceeded("Expanded occurrences exceed 10,000 instance ceiling; narrow query range").3. Structured Recurrence Creation Extension (
src/domain/recurrence_create.rs)rrule: Optional RFC 5545 recurrence rule representation (frequency, count/until, interval, by-day, by-monthday).rdates: Optional list of explicit additional recurrence instants/dates.exdates: Optional list of explicit excluded recurrence instants/dates.VEVENTorVTODOcomponents.invalid_input(HTTP 422, exit code 2).4. Recurrence Conformance Fixture Suite (
tests/calendar_conformance/recurrence_expansion_tests.rs)[2030-04-08T00:00:00Z, 2030-04-13T00:00:00Z):DTSTART: 2030-04-08T09:00:00Z,recurrence_id: 2030-04-08T09:00:00Z.DTSTART: 2030-04-11T11:00:00Z,recurrence_id: 2030-04-10T09:00:00Z.DTSTART: 2030-04-12T09:00:00Z(viaRDATE),recurrence_id: 2030-04-12T09:00:00Z.EXDATE).RECURRENCE-ID.Acceptance Criteria
[2030-04-08T00:00:00Z, 2030-04-13T00:00:00Z), returns occurrences for April 8, April 11 (identity April 10), and April 12; April 9 is excluded; no occurrence at April 10 09:00 remains (Scenario C05, C06).(collection_id, uid, recurrence_id); moving an occurrence'sDTSTARTpreserves its originalRECURRENCE-ID.EXDATEentries and occurrences withSTATUS:CANCELLEDare omitted from the expanded agenda and capacity calculations.limit_exceededwith a smaller-range instruction; silent truncation is forbidden (Scenario C08).RANGE=THISANDFUTUREor unresolvable customVTIMEZONEstructures return explicitcalendar_unsupportedor an explicitly partial agenda withincomplete_calendarwarning; unsupported windows are never declared free (Scenario C07).\r\n) in conformance with RFC 5545 section 3.1.recurrence_idmatching its original recurrence time;recurrence_id: Noneis reserved for the unexpanded master series resource.rrule,rdates, orexdatesproduces a valid RFC 5545 master component, and server expansion generates the conforming occurrence sequence.Notes
Cargo.tomlauthors chore stays pending.coverage.state=unsupported; real outage ->dependency_unavailable).Blocked by
Issue #013: Recurrence Expansion Parser & Conformance Fixturesto Recurrence Expansion Parser & Conformance Fixtures