17 KiB
GovOPlaN Calendar Integration Concept
Module Boundary
govoplan-calendar owns calendar primitives:
- calendar collections
- VEVENT storage and iCalendar import/export
- event recurrence data, recurrence expansion, and recurrence exceptions
- availability and free/busy semantics
- resources such as rooms, shared equipment, and service desks
- groupware calendar adapter boundaries, including CalDAV and Open-Xchange
Calendar does not own meeting-poll decisions, bookable service appointments, task lifecycle, mail transport, or document lifecycle. Those modules should reference calendar events through stable IDs, iCalendar UIDs, capabilities, or API contracts.
First Implementation
The first standalone module provides:
CalendarCollectionrecords for tenant calendarsCalendarEventrecords for VEVENT data- normalized query fields: start, end, summary, location, all-day flag, status, transparency, classification, calendar ID, UID, recurrence ID, sequence, source, and ETag
- iCalendar preservation: raw VEVENT properties, parameters, and generated
text/calendarexport - API endpoints for listing calendars, creating/updating/deleting events, importing iCalendar, and exporting event ICS
- bounded occurrence expansion with RRULE, RDATE, EXDATE, detached overrides, instance/series editing, and free/busy reconciliation
- durable per-user calendar view preferences exposed through the platform Settings surface
- calendar-owned CalDAV sync sources with credential references, scheduled due-sync metadata, full/incremental inbound sync, two-way PUT/DELETE writes, and ETag conflict handling
- a Calendar-owned durable desired-state outbox for two-way CalDAV writes. Event state and the exact external resource snapshot commit atomically; workers use deterministic hrefs, conditional requests, expiring leases, bounded exponential retry, and semantic GET reconciliation after ambiguous outcomes
- WebUI views: month, week, workweek, day, and continuous week-row scrolling
- an Open-Xchange profile over the CalDAV transport, with explicit connector, IDM/group-mapping, and resource-calendar references
The implementation is not yet a full CalDAV network server. It is the internal calendar storage, recurrence, sync, availability, and UI foundation on which scheduling inbox/outbox behavior and CalDAV server endpoints can be built.
Open-Xchange profile
Open-Xchange is an explicit external profile backed by Calendar's CalDAV engine. A profile therefore receives the same bounded discovery, VEVENT round-trip, RRULE/RDATE/EXDATE and detached-exception handling, sync-token/ctag tracking, per-resource ETag conflict detection, and durable two-way outbox as a generic CalDAV source. Calendar's availability capability reads the resulting projection, so free/busy and collision checks use the same recurrence-aware event set. Availability is current as of the source's visible last successful sync; GovOPlaN does not claim live Open-Xchange state while a source is stale.
The source metadata retains three optional, non-secret bindings:
connector_profile_reflinks the endpoint to a Connectors-owned inventory or deployment profile.identity_mapping_reflinks attendee/group identifiers to an IDM- or Access-governed mapping without importing either module.resource_calendar_refidentifies an Open-Xchange room/equipment calendar; the corresponding Calendar collection is owned as aresource.
The calendar.externalProfiles capability lets an optional connector configure
the same profile through a Core contract. Direct Calendar setup remains
available when Connectors is absent. The connector never supplies a Calendar
event model and the references never contain credentials. Open-Xchange
credentials remain a reusable credential-envelope reference or a
Calendar-owned encrypted credential. The WebUI exposes Open-Xchange as a source
type and reuses CalDAV discovery; deployments must enter a DAV endpoint that
resolves to the intended collection.
Outbound delivery operations
No event API or cross-module capability performs CalDAV network I/O inside the
caller's transaction. A local mutation instead creates a
calendar_outbox_operations row containing the exact desired ICS resource,
target href, expected ETag, conflict-policy snapshot, and idempotency key.
The registered govoplan.calendar.dispatch_outbox worker commits an expiring
lease before doing network I/O and commits each outcome independently. If a
worker loses the database connection after a successful remote write, the next
attempt reads the remote object and compares semantic ICS fingerprints. A
matching PUT or an already absent DELETE completes successfully without a blind
duplicate write. Pending updates for one href supersede older never-attempted
rows; attempted predecessors are reconciled first, and updates queued behind an
in-flight write inherit the ETag produced by that write. Delivery and inbound
REPORT application take the same source-row lock,
and only one operation per source is leased at a time. This deliberately trades
per-source throughput for a simple ordering guarantee: a stale inbound report
cannot land after a newer outbound result, and queued leases do not expire while
waiting behind another network request for the same source.
Operators open Outbound changes from a synchronized calendar's settings to inspect a bounded queue and use only the retry, reconcile, or discard actions that are valid for the latest resource generation. Dispatch and due-source routes are worker-only and reject interactive sessions even when the account is an administrator. Terminal ETag conflicts and exhausted retries remain the current local desired state and shield that resource from inbound overwrite until explicitly resolved. Discarding is the administrator's accept-remote transition: it is allowed only for the latest generation, atomically cancels its unresolved predecessor chain, marks the event projection as discarded, clears the sync token, and schedules a full inbound reconciliation so an unchanged remote object is not missed. The UI requires a separate destructive confirmation and explains that accepting remote may lose the unresolved local desired state. Disabled and inbound-only sources still permit discard when the retained source belongs to the tenant, but they cannot be retried or reconciled until made writable again. Missing sources, active worker leases, stale generations, and already-resolved rows expose diagnostics instead of unsafe action buttons.
Disabling outbound delivery or switching to inbound-only is rejected while unresolved desired state exists; retirement is the explicit exception and cancels unresolved work. Endpoint/calendar changes also require no active event bindings or unresolved delivery. Credential rotation does not discard committed desired state. Public event mutation cannot set sync-owned source hrefs, kinds, or ETags. Deleting a synchronized collection or retiring its source is a local unlink: it never deletes the remote collection or its remaining remote events. The unlink immediately scrubs Calendar-owned credential ciphertext and external provider references and audits the deletion. External provider failure blocks retirement before queued work is changed; a later retry tolerates a provider secret already removed by an earlier attempt whose database transaction rolled back. Provider errors and audit details never contain credential values or secret references.
Runtime provider state
Calendar registers tenant-aware runtime state for
calendar.caldav_sync. Each active CalDAV source is projected by a stable
calendar:sync-source:<id> reference with its effective source-authority mode,
enabled state, health, freshness, unresolved-conflict state, recovery readiness,
last successful synchronization, and bounded outbox counts. Collection URLs,
usernames, credential references, remote resource paths, and error text are not
included.
Docs uses the projection to explain configured availability, Ops aggregates it for operator inspection, and configuration-package preflight can require one exact source binding. A source with dead or conflicting desired-state work requires recovery attention; a never-synchronized source remains unknown rather than being reported healthy. The current singular ownership model permits one active sync source per calendar; multi-source fan-in will require per-event source routing. Celery beat triggers recovery every minute, while root-transaction after-commit dispatch provides the normal low-latency path.
Collection retirement and bulk event moves
Collection deletion accepts two explicit, non-destructive external actions for
event_action="move":
detach_keep_remoteapplies only when the source collection is synchronized and the target is local. It moves the local event projections, clears their sync-owned source kind, href, ETag, and CalDAV projection metadata, retires the source locally, and cancels undelivered outbox state. An active delivery lease blocks the transaction. The action never enqueues a remote DELETE, so the remote collection and remote events remain unchanged.copy_to_remoteapplies only when the source is local and the target has an active, enabled, two-way CalDAV source. It moves the local events and commits destination PUT desired states in the durable outbox in the same database transaction. Remote failures therefore remain visible and retryable without rolling back or hiding the local move.
Local-to-local moves retain their existing behavior and do not accept an
external_action. Implicit or mismatched actions and inbound-only destinations
are rejected. remote_move applies only between active, enabled, two-way
CalDAV sources. It is an administrator-authorized durable migration saga:
- the request requires the exact
MOVE REMOTE EVENTSconfirmation and a retained authorization-evidence note; - all destination resources must complete or reconcile their conditional PUTs before any source DELETE can be leased;
- every source DELETE uses the ETag captured when the batch started, so a concurrent remote edit becomes an explicit conflict rather than data loss;
- UIDs are preserved and target UID collisions stop the batch before mutation;
- both calendars, their sources, and moved events reject ordinary edits and synchronization while the batch is active;
- progress, resource states, conflicts, authorization evidence, and actor provenance remain queryable; and
- cancellation is available only before the first source DELETE attempt. It finishes safe destination copies, retains the source resources, and restores source synchronization. Once deletion starts, the batch must be reconciled.
The source collection is retired only after every source resource is confirmed absent. A crash after a destination write is reconciled by semantic ICS content before retry, preserving the outbox's no-blind-repeat guarantee.
Resolved terminal rows (succeeded, superseded, and cancelled) are removed
in bounded batches after CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS (90 days by
default; 0 disables cleanup). conflict and dead rows are never removed by
this cleanup because they still carry unresolved local desired state. Each
periodic or after-commit dispatch also performs one bounded cleanup batch for
the same tenant scope.
Integration Points
Data-subject requests and retention boundaries
Calendar implements the optional privacy.dsar.calendar capability used by
Core's governed data-subject-request workflow. A search is bounded to the
effective tenant and matches normalized organizer or attendee email, direct
membership references, and independently corroborated namespaced Calendar
collection or event references. Only the matching party fragment is projected;
unrelated attendees and unrelated events in the same collection are not copied
into the case.
The projection includes safe event and collection context, personal view preferences, subject-owned synchronization configuration, outbox outcomes, and migration state. It never includes raw ICS or complete iCalendar objects, collection URLs, remote resource hrefs or ETags, sync tokens, usernames, credential references or ciphertext, idempotency keys, worker leases, provider error text, or opaque metadata. An authorized reviewer must use Calendar's own screens when excluded content is necessary to decide the request.
Synchronized, correlated, deleted, queued, and migrated state is classified as retained evidence with an explicit reason. Local collections and events and active credential metadata remain manual-review items because erasure can affect recurrence, other attendees, institutional scheduling, remote systems, and retention duties. The only executable provider action is deletion of the subject's personal view preference; execution locks and revalidates its tenant and owner and is idempotent. Event, attendee, credential, outbox, and migration records are never mutated directly by the DSAR provider. Related meeting-poll, mail, and delivery data remains owned by Scheduling, Poll, Mail, and their own providers.
Scheduling
govoplan-scheduling should use calendar for:
- organizer availability lookup
- candidate slot conflict checks
- final event creation after a poll decision
- participant invitation state as VEVENT attendees when a meeting becomes real
Scheduling remains owner of polls, candidate ranking, external participation links, and decision audit.
Appointments
govoplan-appointments should use calendar for:
- confirmed appointment placement
- staff/resource conflict checks
- room and desk calendars
- cancellation or rescheduling events
Appointments remains owner of public booking flows, service definitions, capacity rules, queues, and no-show handling.
Tasks And Workflow
govoplan-tasks and govoplan-workflow should use calendar for:
- due-date and reminder calendar overlays
- workflow deadlines
- escalation dates
- optional event creation for hearings, reviews, inspections, and internal meetings
Tasks/workflow remain owners of assignment, status, SLA logic, and completion semantics.
Mail And Notifications
govoplan-mail and govoplan-notifications should use calendar for:
- outbound iCalendar invites and updates
- inbound invite parsing from messages
- RSVP state changes
- reminders and digest notifications
Mail remains owner of SMTP/IMAP profiles and mailbox transport. Notifications remains owner of delivery channels and delivery policy.
The versioned calendar.invitations capability is the concrete Campaign/Mail
boundary. Campaign renders and freezes one METHOD:REQUEST attachment per
recipient, then upserts the correlated VEVENT only after delivery acceptance.
Calendar owns attendee PARTSTAT, response timestamps, bounded evidence,
CalDAV outbox state, and batched correlation/summary queries. Mail can forward
METHOD:REPLY parts discovered by an authorized, checkpointed IMAP
delivery-status source. Replaying the same mailbox evidence is idempotent.
Campaign reports read current Calendar state and retain only the invitation
request and mirror result in their own delivery provenance. Recurring Campaign
invitation series remain a separate workflow rather than being inferred from
unrelated recipient rows.
Documents And DMS
govoplan-dms can link documents to events for:
- agendas
- minutes
- attachments
- legal deadlines
- retention and audit context
DMS remains owner of versions, locks, approvals, collaboration, legal hold, and document retention.
Cases, Campaigns, Forms, And Portal
Domain modules may attach calendar references to domain objects:
- cases: hearings, inspections, review meetings
- campaigns: send windows and review deadlines
- forms: submission windows and office-hour sessions
- portal: citizen-facing event or appointment visibility
Calendar should expose reusable event and availability APIs instead of importing those modules.
Connectors And Public-Sector Groupware
govoplan-connectors should own generic connector catalogue behavior, connection setup patterns, and external-system inventory. Calendar owns the actual calendar semantics for:
- CalDAV
- Open-Xchange calendar
- OpenDesk calendar stack integration
- resource-calendar mapping
- free/busy sync
- recurrence and exception mapping
- conflict handling
The connector boundary should hand calendar a configured profile and credentials reference, not a domain-specific event model.
Follow-Up Work
- CalDAV server endpoints if GovOPlaN should expose calendars to external clients rather than only syncing remote collections.
- Open-Xchange adapter that maps OX calendars, attendees, resources, recurrence, and free/busy to the internal calendar model.
- Attendee RSVP workflow and mail/notification bridge.
- Resource calendars for rooms, equipment, counters, and service desks.
- Calendar ACLs beyond tenant-wide permissions.