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
+62
View File
@@ -0,0 +1,62 @@
"""AuditEvent — append-only security & business audit trail.
Audit events are never mutated or deleted. They record WHO did WHAT to
WHICH entity WHEN and from WHERE.
"""
from __future__ import annotations
from typing import Any, Optional, Dict
from pydantic import Field
from asf_domain.base import (
ASFBaseModel,
TenantScopedModel,
ASFFieldID,
UTCDatetime,
AuditAction,
generate_id,
)
class AuditEventBase(ASFBaseModel):
action: AuditAction = Field(..., description="What happened.")
resource_type: str = Field(
...,
max_length=80,
description="Affected entity kind (organization, project, artifact, job, api_key, …).",
)
resource_id: Optional[ASFFieldID] = Field(None, description="Affected entity id if applicable.")
actor_id: Optional[ASFFieldID] = Field(
None, description="User id that caused the event, or null for system actions."
)
actor_type: str = Field(
"user",
max_length=40,
description="user | system | api_key | service — kind of actor that caused the event.",
)
success: bool = True
reason: Optional[str] = Field(
None, max_length=500, description="For failed events, why the action was rejected."
)
class AuditEventCreate(AuditEventBase):
project_id: Optional[ASFFieldID] = None
ip_address: Optional[str] = Field(None, max_length=64)
user_agent: Optional[str] = Field(None, max_length=512)
correlation_id: Optional[str] = Field(None, max_length=128)
metadata: Dict[str, Any] = Field(
default_factory=dict,
description="Structured detail of the change. NEVER log secrets here.",
)
class AuditEvent(TenantScopedModel, AuditEventBase):
id: ASFFieldID = Field(default_factory=generate_id)
project_id: Optional[ASFFieldID] = None
ip_address: Optional[str] = None
user_agent: Optional[str] = None
correlation_id: Optional[str] = None
metadata: Dict[str, Any] = Field(default_factory=dict)
created_at: UTCDatetime = ...