97 lines
2.9 KiB
Python
97 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from typing import Annotated, Literal
|
|
|
|
from pydantic import BaseModel, Field, TypeAdapter
|
|
|
|
from ..errors import ErrorCode, OpName
|
|
|
|
EventPhase = Literal["start", "finish"]
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.now(tz=timezone.utc)
|
|
|
|
|
|
class EventPayloadPolicy(BaseModel):
|
|
"""Controls how much potentially sensitive/large data is included in events."""
|
|
|
|
# Exec output can be noisy and sensitive; default off.
|
|
include_exec_output: bool = Field(default=False)
|
|
|
|
# When enabled, bound output sizes.
|
|
max_stdout_chars: int = Field(default=8_000, ge=0)
|
|
max_stderr_chars: int = Field(default=8_000, ge=0)
|
|
|
|
# For write events, we only include a best-effort byte count (never file bytes).
|
|
include_write_len: bool = Field(default=True)
|
|
|
|
|
|
class SandboxSessionEventBase(BaseModel):
|
|
"""Shared fields for all sandbox audit events."""
|
|
|
|
version: int = Field(default=1)
|
|
|
|
event_id: uuid.UUID = Field(default_factory=uuid.uuid4)
|
|
ts: datetime = Field(default_factory=_utcnow)
|
|
|
|
session_id: uuid.UUID
|
|
seq: int
|
|
|
|
op: OpName
|
|
phase: EventPhase
|
|
|
|
# Correlates start/finish records for an operation.
|
|
# When SDK tracing is active, this is the SDK span id for the operation.
|
|
span_id: str
|
|
parent_span_id: str | None = None
|
|
trace_id: str | None = None
|
|
|
|
# Operation-specific metadata (paths, argv, timings, etc.)
|
|
data: dict[str, object] = Field(default_factory=dict)
|
|
|
|
|
|
class SandboxSessionStartEvent(SandboxSessionEventBase):
|
|
"""The start event for an operation."""
|
|
|
|
phase: Literal["start"] = Field(default="start")
|
|
|
|
|
|
class SandboxSessionFinishEvent(SandboxSessionEventBase):
|
|
"""The finish event for an operation."""
|
|
|
|
phase: Literal["finish"] = Field(default="finish")
|
|
|
|
ok: bool
|
|
duration_ms: float
|
|
|
|
error_code: ErrorCode | None = None
|
|
error_type: str | None = None
|
|
error_message: str | None = None
|
|
error_retryable: bool | None = None
|
|
|
|
# Optional exec outputs (truncated / opt-in via policy).
|
|
stdout: str | None = None
|
|
stderr: str | None = None
|
|
|
|
# Raw exec outputs (bytes) for per-sink/per-op policy application.
|
|
# These are excluded from serialization (JSONL / HTTP) by default.
|
|
stdout_bytes: bytes | None = Field(default=None, exclude=True)
|
|
stderr_bytes: bytes | None = Field(default=None, exclude=True)
|
|
|
|
|
|
# Discriminated union keyed by `phase`.
|
|
SandboxSessionEvent = Annotated[
|
|
SandboxSessionStartEvent | SandboxSessionFinishEvent,
|
|
Field(discriminator="phase"),
|
|
]
|
|
_SANDBOX_SESSION_EVENT_ADAPTER: TypeAdapter[SandboxSessionEvent] = TypeAdapter(SandboxSessionEvent)
|
|
|
|
|
|
def validate_sandbox_session_event(obj: object) -> SandboxSessionEvent:
|
|
"""Parse an event payload (e.g. from JSON) into the correct phase-specific model."""
|
|
|
|
return _SANDBOX_SESSION_EVENT_ADAPTER.validate_python(obj)
|