审计事件标准 struct + 转换 helper(轻量版,v0.5.4 落地)。
目的:让集成方一行代码把 CMDC Agent 运行事件落到 audit_logs 表, 不必为 47 个 EventBus 事件 + 18 个 telemetry 事件各写一段 case 翻译。
字段约定(6 元组核心 + 4 元组扩展)
借鉴 AWS CloudTrail / GitHub Audit Log 等行业稳定 schema:
| 字段 | 类型 | 含义 |
|---|---|---|
actor_type | atom | 谁做了这件事::agent / :user / :plugin / :tool / :system |
actor_id | binary | actor 标识(session_id / account_id / plugin_name / tool_name) |
action | binary | 动作命名空间字符串("agent.turn.finished" / "tool.exec.stop" ...) |
target_type | binary | 目标对象类型("agent_session" / "tool" / "approval" ...) |
target_id | binary | 目标对象标识 |
occurred_at | DateTime.t() | 事件发生时刻(UTC) |
before | term | nil | 变更前状态(可选,变更类事件填充) |
after | term | nil | 变更后状态(可选) |
metadata | map | 附加上下文(duration_ms、token usage、message preview...) |
schema_version | pos_integer | 当前 schema 版本,默认 1,未来字段扩展时升版 |
推荐使用方式
方式 1:订阅 :after_turn Plugin hook(高价值快路径,推荐生产用)
defmodule MyApp.Plugins.AuditSink do
@behaviour CMDC.Plugin
@impl true
def init(opts), do: {:ok, opts}
@impl true
def priority, do: 900
@impl true
def handle_event({:after_turn, payload}, state, ctx) do
audit = CMDC.AuditEvent.from_turn(payload, ctx)
MyApp.AuditLogs.insert!(audit)
{:continue, state}
end
def handle_event(_, state, _), do: {:continue, state}
end一行写一条 audit_log,带本轮 outcome / messages_diff / token_usage_diff / duration,覆盖 80% 审计场景。
方式 2:订阅细粒度 telemetry 事件(细粒度审计)
:telemetry.attach_many(
"audit-sink",
CMDC.Telemetry.all_events(),
fn name, meas, meta, _ ->
case CMDC.AuditEvent.from_telemetry(name, meas, meta) do
nil -> :ok # 不在当前覆盖集
event -> MyApp.AuditLogs.insert!(event)
end
end,
nil
)v0.5.4 轻量版覆盖 6 个核心 telemetry 事件:agent.turn.start/stop /
llm.request.start/stop / tool.exec.start/stop。剩余 12 个 telemetry +
47 个 EventBus 事件的完整映射留 v0.6。
序列化
@derive Jason.Encoder 直接 Jason.encode!(audit) 落 JSON;反序列化用
Jason.decode!(json, keys: :atoms!)(请配套使用 :atoms! 避免 atom table 泄漏)。
与 v0.6 完整版的关系
v0.5.4 是轻量先发版:struct + from_turn + 6 个核心 telemetry 映射; v0.6 完整版会补:
- 47 个 EventBus 事件 → AuditEvent 全表 (
from_event/2) - 12 个细粒度 telemetry 事件映射(plugin pipeline / compactor / checkpoint / subagent / hibernate / provider registry)
- 集成方扩展点(
actor_resolver_fn/target_resolver_fn让业务注入自定义命名)
老 v0.5.4 API 完全向后兼容。
Summary
Functions
从 6 个核心 telemetry 事件构造 AuditEvent。
从 Plugin :after_turn payload + Context 构造 AuditEvent。
返回当前 schema 版本号。
Types
@type actor_type() :: :agent | :user | :plugin | :tool | :system
@type t() :: %CMDC.AuditEvent{ action: String.t(), actor_id: String.t() | nil, actor_type: actor_type(), after: term() | nil, before: term() | nil, metadata: map(), occurred_at: DateTime.t(), schema_version: pos_integer(), target_id: String.t() | nil, target_type: String.t() | nil }
Functions
从 6 个核心 telemetry 事件构造 AuditEvent。
覆盖事件集(v0.5.4 轻量版):
[:cmdc, :agent, :turn, :start]→"agent.turn.start"[:cmdc, :agent, :turn, :stop]→"agent.turn.stop"[:cmdc, :llm, :request, :start]→"llm.request.start"[:cmdc, :llm, :request, :stop]→"llm.request.stop"[:cmdc, :tool, :exec, :start]→"tool.exec.start"[:cmdc, :tool, :exec, :stop]→"tool.exec.stop"
未覆盖的 telemetry 事件返回 nil,让调用方降级处理或自行扩展。完整 18+
事件映射留 v0.6。
参数
event_name— telemetry 事件名 list,如[:cmdc, :tool, :exec, :stop]measurements— telemetry measurements map(duration_ms/tokens_in等)metadata— telemetry metadata map(session_id/tool/model等)
返回
t()— 已识别事件,返回构造好的 AuditEventnil— 未覆盖事件,调用方按需降级
@spec from_turn(map(), CMDC.Context.t() | map()) :: t()
从 Plugin :after_turn payload + Context 构造 AuditEvent。
这是高价值快路径:一轮对话(从 prompt 到 finish / abort)的完整审计, payload 含 outcome / messages_diff / token_usage_diff / duration_ms, 足以重建本轮关键上下文。
参数
payload— Plugin hook 收到的{:after_turn, payload}中的 payload map, 字段见CMDC.Pluginmoduledocafter_turn_payload类型ctx—CMDC.Context.t()或与之 shape 一致的 map(测试场景常用)
示例
def handle_event({:after_turn, payload}, state, ctx) do
audit = CMDC.AuditEvent.from_turn(payload, ctx)
MyApp.AuditLogs.insert!(audit)
{:continue, state}
endmetadata 字段
:duration_ms— 本轮耗时:turn— 本轮序号(从 ctx):abort_reason— 若 outcome=:aborted 时的中止原因:message_count_diff— 本轮新增消息数:first_message_preview/:last_message_preview— 首/末消息文本前 200 字符:token_usage—CMDC.TokenUsage.t()(若 payload 含)
@spec schema_version() :: pos_integer()
返回当前 schema 版本号。
字段集变化时会升版(目前固定为 1)。