first
This commit is contained in:
@@ -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
|
||||
@@ -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] = {}
|
||||
@@ -0,0 +1,19 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "asf-events"
|
||||
version = "0.1.0"
|
||||
description = "ASF event schemas (RabbitMQ payload contracts)"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"asf-domain>=0.1.0",
|
||||
"pydantic>=2.7,<3",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest>=8"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["asf_events*"]
|
||||
Reference in New Issue
Block a user