初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,127 @@
|
||||
"""沙箱调用的一炁事件构造与回写接口。
|
||||
|
||||
事件结构对齐 docs/architecture/data-and-security-architecture.md:``type / source / payload / trace_id /
|
||||
sensitivity / created_at``。当前一炁事件存储尚未实现,``EventSink`` 用 Protocol 抽象,
|
||||
默认实现写 loguru 日志;后续接入真实事件存储时实现 ``EventSink`` 注入即可,无需改动客户端。
|
||||
|
||||
payload 仅记录摘要(argv、输出长度等),不含密钥、Authorization、签名 URL 或完整输出,
|
||||
遵循事件 payload 脱敏约定。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any, Protocol
|
||||
|
||||
from app.core.logging import logger
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SandboxEvent:
|
||||
"""沙箱领域事件(派生自一炁事件契约)。"""
|
||||
|
||||
type: str # tool.started / tool.completed / tool.failed
|
||||
source: str = "tool"
|
||||
trace_id: str | None = None
|
||||
sensitivity: str = "low" # none / low / medium / high
|
||||
payload: dict[str, Any] = field(default_factory=dict)
|
||||
created_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
|
||||
|
||||
|
||||
class EventSink(Protocol):
|
||||
"""事件回写接口。"""
|
||||
|
||||
async def emit(self, event: SandboxEvent) -> None: ...
|
||||
|
||||
|
||||
class LoggingEventSink:
|
||||
"""默认事件回写:输出到 loguru(结构化字段,不含敏感原文)。"""
|
||||
|
||||
async def emit(self, event: SandboxEvent) -> None:
|
||||
logger.bind(
|
||||
event_type=event.type,
|
||||
source=event.source,
|
||||
trace_id=event.trace_id,
|
||||
sensitivity=event.sensitivity,
|
||||
payload=event.payload,
|
||||
).info("sandbox event: {}", event.type)
|
||||
|
||||
|
||||
class CompositeEventSink:
|
||||
"""扇出到多个 sink:一次 emit 顺序广播给所有子 sink。
|
||||
|
||||
供 ToolRuntime 同时落 EventStore(一炁流程事件)与 AuditEvent(治理审计),互不感知。
|
||||
任一子 sink 抛异常会中断后续 sink——审计写入失败应显式暴露(宁可事件漏写也不静默吞
|
||||
异常,便于及时发现 DB 问题)。
|
||||
"""
|
||||
|
||||
def __init__(self, sinks: list[EventSink]) -> None:
|
||||
self._sinks = sinks
|
||||
|
||||
async def emit(self, event: SandboxEvent) -> None:
|
||||
for sink in self._sinks:
|
||||
await sink.emit(event)
|
||||
|
||||
|
||||
def started_event(
|
||||
*,
|
||||
tool: str,
|
||||
argv: list[str],
|
||||
risk_level: str,
|
||||
trace_id: str | None,
|
||||
run_id: str | None,
|
||||
) -> SandboxEvent:
|
||||
return SandboxEvent(
|
||||
type="tool.started",
|
||||
trace_id=trace_id,
|
||||
payload={"tool": tool, "risk_level": risk_level, "run_id": run_id, "argv": argv},
|
||||
)
|
||||
|
||||
|
||||
def completed_event(
|
||||
*,
|
||||
tool: str,
|
||||
trace_id: str | None,
|
||||
run_id: str | None,
|
||||
exit_code: int | None,
|
||||
duration_ms: int,
|
||||
timeout: bool,
|
||||
truncated: bool,
|
||||
stdout_len: int,
|
||||
stderr_len: int,
|
||||
) -> SandboxEvent:
|
||||
return SandboxEvent(
|
||||
type="tool.completed",
|
||||
trace_id=trace_id,
|
||||
payload={
|
||||
"tool": tool,
|
||||
"run_id": run_id,
|
||||
"exit_code": exit_code,
|
||||
"duration_ms": duration_ms,
|
||||
"timeout": timeout,
|
||||
"truncated": truncated,
|
||||
"stdout_len": stdout_len,
|
||||
"stderr_len": stderr_len,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def failed_event(
|
||||
*,
|
||||
tool: str,
|
||||
trace_id: str | None,
|
||||
run_id: str | None,
|
||||
error_code: str,
|
||||
error_message: str,
|
||||
) -> SandboxEvent:
|
||||
return SandboxEvent(
|
||||
type="tool.failed",
|
||||
trace_id=trace_id,
|
||||
payload={
|
||||
"tool": tool,
|
||||
"run_id": run_id,
|
||||
"error_code": error_code,
|
||||
"error_message": error_message,
|
||||
},
|
||||
)
|
||||
Reference in new issue
Block a user