""" Base event classes and idempotency key generation for CloudEvents telemetry. This module provides: - BaseEvent: Abstract base class for all Honcho telemetry events - generate_event_id(): Deterministic event ID generation for idempotency """ import base64 import hashlib from datetime import UTC, datetime from typing import ClassVar from pydantic import BaseModel, Field def generate_event_id( event_type: str, timestamp: datetime, resource_id: str, ) -> str: """Generate a deterministic event ID for idempotency. Same inputs always produce the same ID, so retries are automatically deduplicated on the receiving end. Args: event_type: The CloudEvents type (e.g., "honcho.work.representation.completed") timestamp: When the event occurred resource_id: A unique identifier for the resource (can include workspace_id if relevant) Returns: A deterministic event ID in the format "evt_{base64_hash}" """ payload = f"{event_type}:{resource_id}:{timestamp.isoformat()}" hash_bytes = hashlib.sha256(payload.encode()).digest()[:16] # Use URL-safe base64 encoding, strip padding encoded = base64.urlsafe_b64encode(hash_bytes).decode().rstrip("=") return f"evt_{encoded}" class BaseEvent(BaseModel): """Base class for all Honcho telemetry events. All events inherit from this class and define: - _event_type: The CloudEvents type string - _schema_version: Integer version for schema evolution - _category: Event category (work, activity, resource) Subclasses must implement the abstract class methods by setting class variables and providing get_resource_id(). Subclasses define their own context fields (e.g., workspace_id, workspace_name) as needed - not all events have workspace context. """ # Class variables for event metadata (set by subclasses) _event_type: ClassVar[str] _schema_version: ClassVar[int] _category: ClassVar[str] # "work", "activity", or "resource" # Common timestamp field present in all events timestamp: datetime = Field( default_factory=lambda: datetime.now(UTC), description="When the event occurred (UTC)", ) @classmethod def event_type(cls) -> str: """Return the CloudEvents type string for this event.""" return cls._event_type @classmethod def schema_version(cls) -> int: """Return the schema version for this event.""" return cls._schema_version @classmethod def category(cls) -> str: """Return the event category (work, activity, or resource).""" return cls._category def get_resource_id(self) -> str: """Return the resource ID for idempotency key generation. This should be a unique identifier for the specific operation, such as a task_id or a hash of message IDs. Must be implemented by subclasses. """ raise NotImplementedError("Subclasses must implement get_resource_id()") def generate_id(self) -> str: """Generate a deterministic event ID for this event instance.""" return generate_event_id( event_type=self.event_type(), timestamp=self.timestamp, resource_id=self.get_resource_id(), )