App reference

ADR-0032: Editing a repeating event's rule

Metadata

  • Status: Accepted
  • Date: 2026-08-16
  • Deciders: Eagraí Clainne Team
  • Related: ADR-0009 (service layer authorization), issue #77, issue #94 (EVENT_MOVED)

Context

An event's recurrence was fixed at creation. UpdateEventRequest carried no repeat field, and both edit forms deliberately hid the repeat control: a rule change is not a plain field update. It re-derives every future occurrence, and it collides with the one-off edits a series accumulates — split children (a moved or reworded occurrence) and tombstones (a cancelled slot), both stored as child rows keyed by the slot date they replace.

Meanwhile the common ask is small: end a series earlier, push its until out, or change Tuesday swimming to Thursday. Members had to delete the series and rebuild it, losing its history and its one-off edits.

Decision

The rule is editable for the whole series, through EventService/Update.

  • UpdateEventRequest gains a repeat field (presence = replace the rule, validated like a created rule) and a clear_repeat flag (the series stops repeating; the parent stays as a plain event). No new RPC: the membership guard, the audit trail and the EVENT_MOVED emission are Update's already, and a PermissionMatrix entry for Update exists.
  • The new rule applies from the series start. The series re-derives from its own anchor under the new rule. "Change from here forward" (split the series at today) is deliberately deferred — the split machinery exists (SplitOccurrence), but the two-rule series it implies is a bigger shape than the common ask needs.
  • Child rows survive where their slot survives. A split child or a tombstone whose occurrence_date still lands on a projected slot under the new rule is kept — the one-off edit or cancellation still means something. A child whose slot no longer exists is deleted in the same transaction as the rule write: it refers to an occurrence that no longer happens, and keeping it would strand an orphan on the calendar (stray children are deliberately still rendered — that mechanism is for slots outside the window, not slots outside the rule).
  • A rule change is a move. The members hear EVENT_MOVED (issue #94), because the rule decides where occurrences land exactly as the start time does.

Consequences

  • The common cases — extend or cut until, change the weekday set, widen the interval — are one edit on the series, keeping history, membership and surviving one-off edits.
  • Orphaned child deletions ride the parent's UPDATE mutation and audit entry rather than minting per-child DELETE entries: the user performed one action, the audit log records one action.
  • A rule change under a projected occurrence a member is looking at can make that occurrence vanish; the EVENT_MOVED notification is the compensating signal.
  • The deferred "from here forward" flow can compose later: split the series at a date, then edit the tail's rule with this mechanism.

Alternatives considered

  • A dedicated SetRepeat RPC — a cleaner wire shape, but it would duplicate Update's guard, audit and notification wiring for one field, and need its own matrix entry for the same audience.
  • Discard all child rows on any rule change — simplest to implement and explain, but it silently throws away one-off edits that still make sense under the new rule (cutting until shorter would wipe every past override).
  • Apply the new rule from "here forward" only — the semantically richest option, deferred as above.