"""Base event envelope + job request envelope. Every RabbitMQ message is either: 1. An ``ASFEvent`` subclass → a fact that happened (domain event). 2. A ``JobRequestEnvelope`` → a unit of work to be executed (job). """ from __future__ import annotations from datetime import datetime, timezone from typing import Any, Optional, Dict from uuid import uuid4, UUID from pydantic import Field from asf_domain.base import ASFBaseModel, UTCDatetime, JobType, JobPriority class ASFEvent(ASFBaseModel): """Abstract base for every domain event published on RabbitMQ. Semantics: facts that happened, never commands. Naming pattern: . (artifact.created, deployment.completed). """ event_id: UUID = Field(default_factory=uuid4) event_type: str = Field( ..., max_length=128, description="Dot-delimited routing key e.g. 'artifact.created'.", ) timestamp: UTCDatetime = Field( default_factory=lambda: datetime.now(tz=timezone.utc) ) tenant_id: UUID = Field(..., description="Owning tenant.") project_id: Optional[UUID] = None correlation_id: Optional[str] = Field(None, max_length=128) causation_id: Optional[str] = Field( None, max_length=128, description="Id of the previous event / request that caused this one.", ) payload: Dict[str, Any] = Field(default_factory=dict) class JobRequestEnvelope(ASFBaseModel): """Envelope delivered to a RabbitMQ worker queue. The worker performs the work, then updates the corresponding Mongo ``Job`` record (its id is in this envelope). RabbitMQ is NOT the permanent job state store — MongoDB is. """ job_id: UUID tenant_id: UUID project_id: Optional[UUID] = None type: JobType priority: JobPriority = JobPriority.NORMAL correlation_id: Optional[str] = None payload: Dict[str, Any] = Field(default_factory=dict) reply_to: Optional[str] = Field( None, max_length=255, description="Optional queue/routing-key for completion events.", )