feat(calendar): schedule durable outbox recovery

This commit is contained in:
2026-07-20 16:57:01 +02:00
parent a2320fcb5d
commit 15596f0742
4 changed files with 186 additions and 1 deletions

View File

@@ -0,0 +1,98 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol, runtime_checkable
CAPABILITY_CALENDAR_SCHEDULING = "calendar.scheduling"
CAPABILITY_CALENDAR_OUTBOX = "calendar.outbox"
CALENDAR_AVAILABILITY_READ_SCOPE = "calendar:availability:read"
CALENDAR_EVENT_WRITE_SCOPE = "calendar:event:write"
class CalendarCapabilityError(ValueError):
"""Stable error raised by Calendar capability implementations."""
@dataclass(frozen=True, slots=True)
class CalendarEventRequest:
summary: str
start_at: datetime
calendar_id: str | None = None
description: str | None = None
location: str | None = None
status: str = "CONFIRMED"
transparency: str = "OPAQUE"
classification: str = "PUBLIC"
end_at: datetime | None = None
timezone: str | None = None
attendees: tuple[Mapping[str, object], ...] = ()
categories: tuple[str, ...] = ()
related_to: tuple[Mapping[str, object], ...] = ()
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class CalendarEventRef:
id: str
calendar_id: str
uid: str
external_state: str = "local"
outbox_operation_id: str | None = None
@runtime_checkable
class CalendarSchedulingProvider(Protocol):
def list_freebusy(
self,
session: object,
*,
tenant_id: str,
start_at: datetime,
end_at: datetime,
calendar_ids: Sequence[str] | None = None,
) -> Sequence[Mapping[str, object]]:
...
def create_event(
self,
session: object,
*,
tenant_id: str,
user_id: str | None,
request: CalendarEventRequest,
) -> CalendarEventRef:
...
@runtime_checkable
class CalendarOutboxProvider(Protocol):
"""Stable worker boundary for Calendar-owned external side effects."""
def dispatch_due(
self,
session: object,
*,
tenant_id: str | None = None,
limit: int = 50,
) -> Mapping[str, object]:
...
def calendar_scheduling_provider(registry: object | None) -> CalendarSchedulingProvider | None:
if registry is None or not hasattr(registry, "has_capability"):
return None
if not registry.has_capability(CAPABILITY_CALENDAR_SCHEDULING):
return None
capability = registry.capability(CAPABILITY_CALENDAR_SCHEDULING)
return capability if isinstance(capability, CalendarSchedulingProvider) else None
def calendar_outbox_provider(registry: object | None) -> CalendarOutboxProvider | None:
if registry is None or not hasattr(registry, "has_capability"):
return None
if not registry.has_capability(CAPABILITY_CALENDAR_OUTBOX):
return None
capability = registry.capability(CAPABILITY_CALENDAR_OUTBOX)
return capability if isinstance(capability, CalendarOutboxProvider) else None