Recurrence Expansion Parser & Conformance Fixtures #13

Closed
opened 2026-09-22 17:58:55 +00:00 by yuris · 0 comments
Owner

Summary

Implement the recurrence expansion engine and conformance test fixtures exercising iCalendar recurrence rules (RRULE, RDATE, EXDATE, detached overrides, and RANGE=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 REPORT calendar-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 VCALENDAR stream (containing master series, detached RECURRENCE-ID components, and exception dates), Life must parse, validate, and normalize these streams into distinct, deterministic occurrence items while maintaining strict architectural invariants:

  1. Recurrence Identity Invariant:
    Canonical occurrence identity is (collection_id, uid, recurrence_id). The recurrence_id captures 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), its DTSTART changes, but its RECURRENCE-ID remains 20300410T090000Z. The identity is immutable across rescheduling.
  2. Override Range Invariant:
    A query for a half-open window [from, to) must accurately reflect overrides that move across window boundaries:
    • An override moved into the query range from outside the range must be included in the agenda.
    • An override moved out of the query range to a date outside the window must be excluded from the agenda without duplicating the master instance.
    • Cancelled occurrences (STATUS:CANCELLED) must be excluded from busy capacity and agenda display.
    • EXDATE entries must be strictly excluded from the expanded set.
  3. Unsupported Recurrence Fail-Safe:
    If a CalDAV server or local parser encounters recurrence edge cases that cannot be expanded losslessly—such as RANGE=THISANDFUTURE or unmapped custom VTIMEZONE definitions—the system MUST NOT pretend that the master-only representation is complete. It must return an explicit calendar_unsupported error or emit an explicitly partial agenda with incomplete_calendar warnings. An unsupported temporal window can never be labeled as free capacity.
  4. Finite Bound Invariant:
    All expansions must enforce half-open [from, to) bounds. Queries exceeding a 93-day span or generating >10,000 instances must return limit_exceeded with 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):

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Life fictional fixture//EN
BEGIN:VEVENT
UID:fictional-series
DTSTAMP:20300401T000000Z
DTSTART:20300408T090000Z
DTEND:20300408T100000Z
RRULE:FREQ=DAILY;COUNT=3
RDATE:20300412T090000Z
EXDATE:20300409T090000Z
SUMMARY:Fictional focus block
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
UID:fictional-series
DTSTAMP:20300401T000000Z
RECURRENCE-ID:20300410T090000Z
DTSTART:20300411T110000Z
DTEND:20300411T120000Z
SUMMARY:Fictional moved block
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR

Normative expansion assertion (verification.md, line 81):

"Expected expansion in [2030-04-08T00:00:00Z,2030-04-13T00:00:00Z): April 8 at 09:00, April 11 at 11:00 (identity April 10 at 09:00), April 12 at 09:00. April 9 excluded; no original April 10 block remains."

Verification Matrix Scenarios

From docs/technical/verification.md (lines 39–42):

ID Input / failure Observable acceptance
C05 RRULE plus RDATE and EXDATE Included/excluded occurrences correct, no duplicates, bounded expansion
C06 Detached occurrence moved into range from outside, another moved out, another cancelled Correct visible/busy set, original recurrence identities retained
C07 RANGE=THISANDFUTURE, custom VTIMEZONE and DST transition Exact deployment conformance or explicit calendar_unsupported/partial; no unsupported window labeled free
C08 Infinite recurrence, >93-day query or >10,000 instances Explicit limit_exceeded and smaller-range instruction, no silent truncation

Recurrence & Mutation Contract

From docs/technical/calendar.md (lines 31–36):

"Expansion must honor RRULE, RDATE, EXDATE, cancellations and detached overrides. Occurrence identity is collection+UID+original RECURRENCE-ID, including value type/timezone semantics; a moved occurrence's actual DTSTART differs without changing its identity. Retrieval must find overrides moved into the query range from outside and exclude moved-out/cancelled occurrences without duplication. Keep the raw master/override set for all writes, even when display uses expanded instances.

Request bounded calendar-data expansion through REPORT and normalize its results. The adapter must validate returned bounds and expansion completeness, not assume support because the request succeeded. Required cases include RANGE=THISANDFUTURE. If the server cannot expand a required case, the deployment is incompatible with complete agenda/planning until corrected: calendar_unsupported or explicitly partial agenda, never master-only output presented as complete."

Conformance Gates

From docs/technical/calendar.md (lines 43–53):

"Before claiming complete agenda/planning on a deployment, test its exact Radicale version and collection capabilities against:

  1. Date-only, floating, UTC and TZID/VTIMEZONE inputs, DST fold/gap and local-midnight boundaries.
  2. RRULE/RDATE/EXDATE, finite/infinite bounds, duplicate inclusion, cancelled and detached/moved occurrences, RANGE=THISANDFUTURE.
  3. VTODO date-only deadlines, undated tasks and occurrence-specific completion; VEVENT all-day/exclusive end and transparency.
  4. Full-resource preservation of unknown properties/parameters, alarms and overrides on conditional edits.
  5. ETag races, sync cursor invalidation, per-resource errors and concurrent REPORT/GET changes.

Failure blocks the affected claimed feature. An incomplete read may be shown with warnings, but cannot underpin availability allocation."

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)

  • Implement RecurrenceNormalizer that ingests raw multi-component VCALENDAR streams (parsed via ical or icalendar).
  • Distinguish master components (VEVENT / VTODO without RECURRENCE-ID) from detached overrides (VEVENT / VTODO with RECURRENCE-ID).
  • Implement canonical RecurrenceId representation:
    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
    pub enum RecurrenceId {
        Date(chrono::NaiveDate),
        DateTime(chrono::DateTime<chrono::Utc>),
        DateTimeTzid {
            instant: chrono::DateTime<chrono::Utc>,
            tzid: String,
        },
    }
    
  • Link overrides to master series by matching UID. If an override arrives without a matching master in the resource stream, handle it gracefully without crashing, logging diagnostic context.
  • Server-Side Expansion Normalization & Validation:
    • Bounded server-side CalDAV calendar-data expansion is the primary recurrence engine (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.
    • Verify that server-expanded occurrences conform to the series expansion set: (\text{RRULE occurrences} \cup \text{RDATE}) \setminus \text{EXDATE}.
    • Match detached override components to their corresponding server occurrence via RECURRENCE-ID to confirm that the server applied override properties (such as relocated DTSTART).
    • Assign every expanded occurrence its original recurrence identity (recurrence_id), preserving the original recurrence value type and timezone context. The first occurrence in the series has recurrence_id set to its scheduled DTSTART/DUE instant (2030-04-08T09:00:00Z), distinguishing the occurrence from the unexpanded master series resource (recurrence_id: None).
    • Drop any instance marked with STATUS:CANCELLED from agenda display and capacity reservation.
    • Filter against query bounds [from, to): include detached overrides moved into [from, to) even if the original RECURRENCE-ID fell outside; exclude overrides moved outside [from, to) even if the original RECURRENCE-ID fell inside.
  • Detect unsupported recurrence features:
    • If a component specifies RANGE=THISANDFUTURE or RANGE=THISANDPRIOR, and the server expansion does not expand it, return CalendarError::Unsupported("calendar_unsupported").
    • If an embedded custom VTIMEZONE cannot be mapped or resolved to IANA timezone rules, return CalendarError::Unsupported("calendar_unsupported").

2. Query Span & Instance Bounding

  • Enforce the 93-day window limit:
    • If to - from > 93\text{ days}, reject immediately with CalendarError::LimitExceeded ("Query span exceeds 93 days maximum limit; narrow query range").
  • Enforce the 10,000 instance ceiling:
    • If expanded instances exceed 10,000, abort processing and return CalendarError::LimitExceeded ("Expanded occurrences exceed 10,000 instance ceiling; narrow query range").

3. Structured Recurrence Creation Extension (src/domain/recurrence_create.rs)

  • Extend task and event creation models (#010) with optional structured recurrence fields:
    • 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.
  • When present, the new-item builder writes these directly to master VEVENT or VTODO components.
  • Conformance validation rejects malformed rules, unsupported frequencies, or inconsistent date value types with invalid_input (HTTP 422, exit code 2).

4. Recurrence Conformance Fixture Suite (tests/calendar_conformance/recurrence_expansion_tests.rs)

  • Create integration tests loading the normative fixture with explicit CRLF line endings.
  • Validate exact instance set in query range [2030-04-08T00:00:00Z, 2030-04-13T00:00:00Z):
    1. Occurrence 1: DTSTART: 2030-04-08T09:00:00Z, recurrence_id: 2030-04-08T09:00:00Z.
    2. Occurrence 2: DTSTART: 2030-04-11T11:00:00Z, recurrence_id: 2030-04-10T09:00:00Z.
    3. Occurrence 3: DTSTART: 2030-04-12T09:00:00Z (via RDATE), recurrence_id: 2030-04-12T09:00:00Z.
    4. Assert April 9 is omitted (EXDATE).
    5. Assert April 10 at 09:00 is omitted (overridden and moved to April 11).
  • Test detached override moved out of range: Verify that when an override moves outside the window, neither the old nor the new occurrence appears.
  • Test detached override moved into range: Verify that when an override from outside the window moves inside, it appears with its original out-of-range RECURRENCE-ID.

Acceptance Criteria

  • Recurrence expansion fixture passes verbatim: in window [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).
  • Canonical recurrence identity is verified as (collection_id, uid, recurrence_id); moving an occurrence's DTSTART preserves its original RECURRENCE-ID.
  • Overrides moved into the query window from outside are included; overrides moved out of the window are excluded without duplicating master instances (Scenario C06).
  • EXDATE entries and occurrences with STATUS:CANCELLED are omitted from the expanded agenda and capacity calculations.
  • Query span >93 days or instance count >10,000 triggers limit_exceeded with a smaller-range instruction; silent truncation is forbidden (Scenario C08).
  • RANGE=THISANDFUTURE or unresolvable custom VTIMEZONE structures return explicit calendar_unsupported or an explicitly partial agenda with incomplete_calendar warning; unsupported windows are never declared free (Scenario C07).
  • Line endings in test fixtures normalize CRLF (\r\n) in conformance with RFC 5545 section 3.1.
  • Every expanded occurrence possesses a non-null recurrence_id matching its original recurrence time; recurrence_id: None is reserved for the unexpanded master series resource.
  • Creating a recurring item with structured rrule, rdates, or exdates produces a valid RFC 5545 master component, and server expansion generates the conforming occurrence sequence.

Notes

  • No dependency added, so the deferred Cargo.toml authors chore stays pending.
  • Coverage refinement: capability failures and query-bound failures are no longer counted as collection outages (unsupported collection -> explicit partial with coverage.state=unsupported; real outage -> dependency_unavailable).

Blocked by

  • #008: Bounded CalDAV REPORT Calendar-Query & Timezone Expansion
  • #011: Conditional Item Updates with Quoted ETag Preflight
## Summary Implement the recurrence expansion engine and conformance test fixtures exercising iCalendar recurrence rules (`RRULE`, `RDATE`, `EXDATE`, detached overrides, and `RANGE=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 `REPORT` calendar-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 `VCALENDAR` stream (containing master series, detached `RECURRENCE-ID` components, and exception dates), Life must parse, validate, and normalize these streams into distinct, deterministic occurrence items while maintaining strict architectural invariants: 1. **Recurrence Identity Invariant:** Canonical occurrence identity is `(collection_id, uid, recurrence_id)`. The `recurrence_id` captures 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), its `DTSTART` changes, but its `RECURRENCE-ID` remains `20300410T090000Z`. The identity is immutable across rescheduling. 2. **Override Range Invariant:** A query for a half-open window `[from, to)` must accurately reflect overrides that move across window boundaries: - An override moved *into* the query range from outside the range must be included in the agenda. - An override moved *out* of the query range to a date outside the window must be excluded from the agenda without duplicating the master instance. - Cancelled occurrences (`STATUS:CANCELLED`) must be excluded from busy capacity and agenda display. - `EXDATE` entries must be strictly excluded from the expanded set. 3. **Unsupported Recurrence Fail-Safe:** If a CalDAV server or local parser encounters recurrence edge cases that cannot be expanded losslessly—such as `RANGE=THISANDFUTURE` or unmapped custom `VTIMEZONE` definitions—the system MUST NOT pretend that the master-only representation is complete. It must return an explicit `calendar_unsupported` error or emit an explicitly partial agenda with `incomplete_calendar` warnings. An unsupported temporal window can never be labeled as free capacity. 4. **Finite Bound Invariant:** All expansions must enforce half-open `[from, to)` bounds. Queries exceeding a 93-day span or generating >10,000 instances must return `limit_exceeded` with 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): ```ics BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Life fictional fixture//EN BEGIN:VEVENT UID:fictional-series DTSTAMP:20300401T000000Z DTSTART:20300408T090000Z DTEND:20300408T100000Z RRULE:FREQ=DAILY;COUNT=3 RDATE:20300412T090000Z EXDATE:20300409T090000Z SUMMARY:Fictional focus block STATUS:CONFIRMED END:VEVENT BEGIN:VEVENT UID:fictional-series DTSTAMP:20300401T000000Z RECURRENCE-ID:20300410T090000Z DTSTART:20300411T110000Z DTEND:20300411T120000Z SUMMARY:Fictional moved block STATUS:CONFIRMED END:VEVENT END:VCALENDAR ``` Normative expansion assertion (`verification.md`, line 81): > "Expected expansion in `[2030-04-08T00:00:00Z,2030-04-13T00:00:00Z)`: April 8 at 09:00, April 11 at 11:00 (identity April 10 at 09:00), April 12 at 09:00. April 9 excluded; no original April 10 block remains." ### Verification Matrix Scenarios From `docs/technical/verification.md` (lines 39–42): | ID | Input / failure | Observable acceptance | |---|---|---| | C05 | RRULE plus RDATE and EXDATE | Included/excluded occurrences correct, no duplicates, bounded expansion | | C06 | Detached occurrence moved into range from outside, another moved out, another cancelled | Correct visible/busy set, original recurrence identities retained | | C07 | RANGE=THISANDFUTURE, custom VTIMEZONE and DST transition | Exact deployment conformance or explicit calendar_unsupported/partial; no unsupported window labeled free | | C08 | Infinite recurrence, >93-day query or >10,000 instances | Explicit limit_exceeded and smaller-range instruction, no silent truncation | ### Recurrence & Mutation Contract From `docs/technical/calendar.md` (lines 31–36): > "Expansion must honor RRULE, RDATE, EXDATE, cancellations and detached overrides. Occurrence identity is collection+UID+original RECURRENCE-ID, including value type/timezone semantics; a moved occurrence's actual DTSTART differs without changing its identity. Retrieval must find overrides moved into the query range from outside and exclude moved-out/cancelled occurrences without duplication. Keep the raw master/override set for all writes, even when display uses expanded instances. > > Request bounded calendar-data expansion through REPORT and normalize its results. The adapter must validate returned bounds and expansion completeness, not assume support because the request succeeded. Required cases include RANGE=THISANDFUTURE. If the server cannot expand a required case, the deployment is incompatible with complete agenda/planning until corrected: calendar_unsupported or explicitly partial agenda, never master-only output presented as complete." ### Conformance Gates From `docs/technical/calendar.md` (lines 43–53): > "Before claiming complete agenda/planning on a deployment, test its exact Radicale version and collection capabilities against: > 1. Date-only, floating, UTC and TZID/VTIMEZONE inputs, DST fold/gap and local-midnight boundaries. > 2. RRULE/RDATE/EXDATE, finite/infinite bounds, duplicate inclusion, cancelled and detached/moved occurrences, RANGE=THISANDFUTURE. > 3. VTODO date-only deadlines, undated tasks and occurrence-specific completion; VEVENT all-day/exclusive end and transparency. > 4. Full-resource preservation of unknown properties/parameters, alarms and overrides on conditional edits. > 5. ETag races, sync cursor invalidation, per-resource errors and concurrent REPORT/GET changes. > > Failure blocks the affected claimed feature. An incomplete read may be shown with warnings, but cannot underpin availability allocation." ### 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`) - Implement `RecurrenceNormalizer` that ingests raw multi-component `VCALENDAR` streams (parsed via `ical` or `icalendar`). - Distinguish master components (`VEVENT` / `VTODO` without `RECURRENCE-ID`) from detached overrides (`VEVENT` / `VTODO` with `RECURRENCE-ID`). - Implement canonical `RecurrenceId` representation: ```rust #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum RecurrenceId { Date(chrono::NaiveDate), DateTime(chrono::DateTime<chrono::Utc>), DateTimeTzid { instant: chrono::DateTime<chrono::Utc>, tzid: String, }, } ``` - Link overrides to master series by matching `UID`. If an override arrives without a matching master in the resource stream, handle it gracefully without crashing, logging diagnostic context. - Server-Side Expansion Normalization & Validation: - Bounded server-side CalDAV calendar-data expansion is the primary recurrence engine (`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. - Verify that server-expanded occurrences conform to the series expansion set: $(\text{RRULE occurrences} \cup \text{RDATE}) \setminus \text{EXDATE}$. - Match detached override components to their corresponding server occurrence via `RECURRENCE-ID` to confirm that the server applied override properties (such as relocated `DTSTART`). - Assign every expanded occurrence its original recurrence identity (`recurrence_id`), preserving the original recurrence value type and timezone context. The first occurrence in the series has `recurrence_id` set to its scheduled DTSTART/DUE instant (`2030-04-08T09:00:00Z`), distinguishing the occurrence from the unexpanded master series resource (`recurrence_id: None`). - Drop any instance marked with `STATUS:CANCELLED` from agenda display and capacity reservation. - Filter against query bounds $[from, to)$: include detached overrides moved into $[from, to)$ even if the original `RECURRENCE-ID` fell outside; exclude overrides moved outside $[from, to)$ even if the original `RECURRENCE-ID` fell inside. - Detect unsupported recurrence features: - If a component specifies `RANGE=THISANDFUTURE` or `RANGE=THISANDPRIOR`, and the server expansion does not expand it, return `CalendarError::Unsupported("calendar_unsupported")`. - If an embedded custom `VTIMEZONE` cannot be mapped or resolved to IANA timezone rules, return `CalendarError::Unsupported("calendar_unsupported")`. ### 2. Query Span & Instance Bounding - Enforce the 93-day window limit: - If $to - from > 93\text{ days}$, reject immediately with `CalendarError::LimitExceeded` ("Query span exceeds 93 days maximum limit; narrow query range"). - Enforce the 10,000 instance ceiling: - If expanded instances exceed 10,000, abort processing and return `CalendarError::LimitExceeded` ("Expanded occurrences exceed 10,000 instance ceiling; narrow query range"). ### 3. Structured Recurrence Creation Extension (`src/domain/recurrence_create.rs`) - Extend task and event creation models (#010) with optional structured recurrence fields: - `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. - When present, the new-item builder writes these directly to master `VEVENT` or `VTODO` components. - Conformance validation rejects malformed rules, unsupported frequencies, or inconsistent date value types with `invalid_input` (HTTP 422, exit code 2). ### 4. Recurrence Conformance Fixture Suite (`tests/calendar_conformance/recurrence_expansion_tests.rs`) - Create integration tests loading the normative fixture with explicit CRLF line endings. - Validate exact instance set in query range `[2030-04-08T00:00:00Z, 2030-04-13T00:00:00Z)`: 1. Occurrence 1: `DTSTART: 2030-04-08T09:00:00Z`, `recurrence_id: 2030-04-08T09:00:00Z`. 2. Occurrence 2: `DTSTART: 2030-04-11T11:00:00Z`, `recurrence_id: 2030-04-10T09:00:00Z`. 3. Occurrence 3: `DTSTART: 2030-04-12T09:00:00Z` (via `RDATE`), `recurrence_id: 2030-04-12T09:00:00Z`. 4. Assert April 9 is omitted (`EXDATE`). 5. Assert April 10 at 09:00 is omitted (overridden and moved to April 11). - Test detached override moved *out* of range: Verify that when an override moves outside the window, neither the old nor the new occurrence appears. - Test detached override moved *into* range: Verify that when an override from outside the window moves inside, it appears with its original out-of-range `RECURRENCE-ID`. ## Acceptance Criteria - [x] Recurrence expansion fixture passes verbatim: in window `[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). - [x] Canonical recurrence identity is verified as `(collection_id, uid, recurrence_id)`; moving an occurrence's `DTSTART` preserves its original `RECURRENCE-ID`. - [x] Overrides moved into the query window from outside are included; overrides moved out of the window are excluded without duplicating master instances (Scenario C06). - [x] `EXDATE` entries and occurrences with `STATUS:CANCELLED` are omitted from the expanded agenda and capacity calculations. - [x] Query span >93 days or instance count >10,000 triggers `limit_exceeded` with a smaller-range instruction; silent truncation is forbidden (Scenario C08). - [x] `RANGE=THISANDFUTURE` or unresolvable custom `VTIMEZONE` structures return explicit `calendar_unsupported` or an explicitly partial agenda with `incomplete_calendar` warning; unsupported windows are never declared free (Scenario C07). - [x] Line endings in test fixtures normalize CRLF (`\r\n`) in conformance with RFC 5545 section 3.1. - [x] Every expanded occurrence possesses a non-null `recurrence_id` matching its original recurrence time; `recurrence_id: None` is reserved for the unexpanded master series resource. - [x] Creating a recurring item with structured `rrule`, `rdates`, or `exdates` produces a valid RFC 5545 master component, and server expansion generates the conforming occurrence sequence. ## Notes - No dependency added, so the deferred `Cargo.toml` authors chore stays pending. - Coverage refinement: capability failures and query-bound failures are no longer counted as collection outages (unsupported collection -> explicit partial with `coverage.state=unsupported`; real outage -> `dependency_unavailable`). ## Blocked by - #008: Bounded CalDAV REPORT Calendar-Query & Timezone Expansion - #011: Conditional Item Updates with Quoted ETag Preflight
yuris changed title from Issue #013: Recurrence Expansion Parser & Conformance Fixtures to Recurrence Expansion Parser & Conformance Fixtures 2026-09-22 20:02:38 +00:00
yuris closed this issue 2026-09-22 22:20:34 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service#13
No description provided.