This commit is contained in:
valenti
2026-09-08 07:21:34 -04:00
commit a4b224da8e
169 changed files with 27483 additions and 0 deletions
@@ -0,0 +1,55 @@
"""asf-events: canonical contracts for every message that travels on the
ASF RabbitMQ backbone (both domain events and job-request envelopes).
Service code NEVER constructs a message dict inline: always use one of
the models in this package. This keeps producers and consumers typed.
"""
__version__ = "0.1.0"
from asf_events.base import ASFEvent, JobRequestEnvelope
from asf_events.artifact_events import (
ArtifactCreated,
ArtifactUpdated,
ArtifactDeleted,
ArtifactRelationshipCreated,
ArtifactRelationshipDeleted,
)
from asf_events.ai_events import (
AIInferenceRequested,
AIInferenceCompleted,
AIInferenceFailed,
)
from asf_events.identity_events import (
OrganizationCreated,
ProjectCreated,
ProjectUpdated,
)
from asf_events.job_events import (
JobQueued,
JobStarted,
JobCompleted,
JobFailed,
JobCancelled,
)
__all__ = [
"ASFEvent",
"JobRequestEnvelope",
"ArtifactCreated",
"ArtifactUpdated",
"ArtifactDeleted",
"ArtifactRelationshipCreated",
"ArtifactRelationshipDeleted",
"AIInferenceRequested",
"AIInferenceCompleted",
"AIInferenceFailed",
"OrganizationCreated",
"ProjectCreated",
"ProjectUpdated",
"JobQueued",
"JobStarted",
"JobCompleted",
"JobFailed",
"JobCancelled",
]
@@ -0,0 +1,41 @@
"""AI-specific domain events (published by the AI Gateway / workers)."""
from __future__ import annotations
from typing import Any, Dict, Optional
from uuid import UUID
from asf_events.base import ASFEvent
class AIInferenceRequested(ASFEvent):
event_type: str = "ai.inference.requested"
job_id: Optional[UUID] = None
capability: str
provider: str
preferred_model: Optional[str] = None
payload: Dict[str, Any]
class AIInferenceCompleted(ASFEvent):
event_type: str = "ai.inference.completed"
job_id: Optional[UUID] = None
execution_id: UUID
capability: str
provider: str
model_used: str
duration_ms: Optional[int] = None
input_tokens: Optional[int] = None
output_tokens: Optional[int] = None
estimated_cost: Optional[float] = None
payload: Dict[str, Any]
class AIInferenceFailed(ASFEvent):
event_type: str = "ai.inference.failed"
job_id: Optional[UUID] = None
execution_id: UUID
capability: str
provider: str
error: str
payload: Dict[str, Any] = {}
@@ -0,0 +1,51 @@
"""Artifact + ArtifactRelationship domain events."""
from __future__ import annotations
from typing import Any, Dict, Optional
from uuid import UUID
from asf_events.base import ASFEvent
class ArtifactCreated(ASFEvent):
event_type: str = "artifact.created"
artifact_id: UUID
artifact_type: str
project_id: UUID
payload: Dict[str, Any]
class ArtifactUpdated(ASFEvent):
event_type: str = "artifact.updated"
artifact_id: UUID
artifact_type: str
project_id: UUID
changed_fields: list[str]
previous_version: int
payload: Dict[str, Any]
class ArtifactDeleted(ASFEvent):
event_type: str = "artifact.deleted"
artifact_id: UUID
artifact_type: str
project_id: UUID
class ArtifactRelationshipCreated(ASFEvent):
event_type: str = "artifact.relationship.created"
relationship_id: UUID
source_artifact_id: UUID
target_artifact_id: UUID
relationship_type: str
source_project_id: UUID
target_project_id: UUID
payload: Dict[str, Any] = {}
class ArtifactRelationshipDeleted(ASFEvent):
event_type: str = "artifact.relationship.deleted"
relationship_id: UUID
source_artifact_id: UUID
target_artifact_id: UUID
+65
View File
@@ -0,0 +1,65 @@
"""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: <Noun>.<Ved> (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.",
)
@@ -0,0 +1,32 @@
"""Organization / Project domain events."""
from __future__ import annotations
from typing import Any, Dict
from uuid import UUID
from asf_events.base import ASFEvent
class OrganizationCreated(ASFEvent):
event_type: str = "organization.created"
organization_id: UUID
slug: str
name: str
payload: Dict[str, Any] = {}
class ProjectCreated(ASFEvent):
event_type: str = "project.created"
project_id: UUID
organization_id: UUID
project_key: str
name: str
payload: Dict[str, Any] = {}
class ProjectUpdated(ASFEvent):
event_type: str = "project.updated"
project_id: UUID
changed_fields: list[str]
payload: Dict[str, Any] = {}
@@ -0,0 +1,44 @@
"""Job lifecycle domain events."""
from __future__ import annotations
from typing import Any, Dict, Optional
from uuid import UUID
from asf_events.base import ASFEvent
class JobQueued(ASFEvent):
event_type: str = "job.queued"
job_id: UUID
job_type: str
queue: str
payload: Dict[str, Any] = {}
class JobStarted(ASFEvent):
event_type: str = "job.started"
job_id: UUID
worker: Optional[str] = None
payload: Dict[str, Any] = {}
class JobCompleted(ASFEvent):
event_type: str = "job.completed"
job_id: UUID
duration_ms: Optional[int] = None
payload: Dict[str, Any] = {}
class JobFailed(ASFEvent):
event_type: str = "job.failed"
job_id: UUID
error: str
payload: Dict[str, Any] = {}
class JobCancelled(ASFEvent):
event_type: str = "job.cancelled"
job_id: UUID
reason: Optional[str] = None
payload: Dict[str, Any] = {}