CMDC.AuditEvent (cmdc v0.5.4)

Copy Markdown View Source

审计事件标准 struct + 转换 helper(轻量版,v0.5.4 落地)。

目的:让集成方一行代码把 CMDC Agent 运行事件落到 audit_logs 表, 不必为 47 个 EventBus 事件 + 18 个 telemetry 事件各写一段 case 翻译。

字段约定(6 元组核心 + 4 元组扩展)

借鉴 AWS CloudTrail / GitHub Audit Log 等行业稳定 schema:

字段类型含义
actor_typeatom谁做了这件事::agent / :user / :plugin / :tool / :system
actor_idbinaryactor 标识(session_id / account_id / plugin_name / tool_name)
actionbinary动作命名空间字符串("agent.turn.finished" / "tool.exec.stop" ...)
target_typebinary目标对象类型("agent_session" / "tool" / "approval" ...)
target_idbinary目标对象标识
occurred_atDateTime.t()事件发生时刻(UTC)
beforeterm | nil变更前状态(可选,变更类事件填充)
afterterm | nil变更后状态(可选)
metadatamap附加上下文(duration_ms、token usage、message preview...)
schema_versionpos_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

actor_type()

@type actor_type() :: :agent | :user | :plugin | :tool | :system

t()

@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

from_telemetry(event_name, measurements, metadata)

@spec from_telemetry([atom()], map(), map()) :: t() | nil

从 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() — 已识别事件,返回构造好的 AuditEvent
  • nil — 未覆盖事件,调用方按需降级

from_turn(payload, ctx)

@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.Plugin moduledoc after_turn_payload 类型
  • ctxCMDC.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}
end

metadata 字段

  • :duration_ms — 本轮耗时
  • :turn — 本轮序号(从 ctx)
  • :abort_reason — 若 outcome=:aborted 时的中止原因
  • :message_count_diff — 本轮新增消息数
  • :first_message_preview / :last_message_preview — 首/末消息文本前 200 字符
  • :token_usageCMDC.TokenUsage.t()(若 payload 含)

schema_version()

@spec schema_version() :: pos_integer()

返回当前 schema 版本号。

字段集变化时会升版(目前固定为 1)。