dsh-audit-log
> 谁改了我的数据流? — DeepSeek Harness 插件运行时数据流审计日志,支持精确到插件与 fiber 的归因。
[English](../README.md) | 简体中文
dsh-audit-log 回答插件生态迟早会遇到的问题:当会话数据里的某个字段消失或被改写时,是哪个插件改的、按什么顺序、具体改了什么?
它是只读观察者:从不阻塞、从不改写、从不存储负载值——只记录结构指纹(类型、键、长度)。秘密永不进入日志。
为什么需要
Cordis(DeepSeek Harness 的底层框架)在 waterfall 分发中让所有 listener 共享同一个 args 数组,且 internal/get / internal/set 拦截点是公开的。任何插件都能静默改写流经系统的数据。当插件数以百计时,"我的字段被哪个插件清洗了"几乎无法回答。
dsh-audit-log 让这个问题可回答:
- 分发级差分 — 每次被审计的分发都在前后快照参数形状,记录期间的变更。
- 逐 listener 窗口归因 — 注册时包裹每个 listener;变更精确归因到执行它的插件 + fiber(
confidence: "window"),而非启发式猜测。
安装
# 装进你的 web profile
cd ~/.dsh/profiles/web
pnpm add dsh-audit-log然后在 package.json 的 dsh.profile.bundles 里加入 dsh-audit-log(或使用 dsh plugin --profile web add dsh-audit-log),重启 dsh 实例。
插件会注册 ctx.auditLog 并立即开始记录,基础使用无需任何配置。
用法
从任意插件查询审计轨迹:
const records = await ctx.auditLog.query({
events: ['message/send'],
mutationsOnly: true, // 只保留改过数据的分发
fromSeq: 100,
limit: 50,
})
// attribute_by_window 开启时,每条 mutation 带精确归因:
// { listenerIndex, package, fiber, confidence: 'window' }
const culprit = records[0].mutations[0].attribution?.package
// 原始逐 listener 窗口记录:
const windows = ctx.auditLog.queryWindows({ event: 'message/send' })记录结构
{
"v": 1, "ts": "2026-08-19T08:00:00.000Z", "seq": 42,
"mode": "waterfall", "event": "message/send",
"listeners": [{ "order": 0, "package": "my-plugin", "fiber": 3 }],
"before": [ { "type": "object", "keys": ["content"] } ],
"after": [ { "type": "object", "keys": ["content"] } ],
"mutations": [{
"argIndex": 0, "path": "arg[0].content",
"kind": "replace", "beforeLength": 56, "afterLength": 36,
"attribution": { "listenerIndex": 1, "package": "spam-filter", "fiber": 7, "confidence": "window" }
}]
}配置
通过 profile 的 patch 层(cordis.patch.yml),或 $DSH_HOME/settings.yaml 的 audit-log 命名空间:
| 字段 | 默认值 | 含义 |
|---|---|---|
enabled | true | 总开关 |
capacity | 10000 | 环形缓冲大小(最旧记录先被覆盖) |
mutations_only | false | 只保留产生过变更的分发 |
attribute_by_window | true | 逐 listener 归因(包裹 listener) |
package_allowlist | [] | 包名正则白名单;空 = 审计全部 |
package_blocklist | [] | 包名正则黑名单;在 allowlist 之后排除 |
events | [] | 要审计的精确事件名;空 = 全部 |
event_allowlist | [] | 事件名正则白名单 |
- id: audit-log
config:
events: ["message/send", "before/*"]
package_blocklist: ["my-noisy-plugin"]工作原理
dispatch → internal/dispatch (prepend) → fingerprint(args) → listeners 执行 → fingerprint(args) → diff → store1. 分发级差分(P0/P1) — Cordis 在公开 listener 运行*之前*同步触发 internal/dispatch。本服务挂载这一个 hook(prepend: true, global: true),快照参数形状,放行分发,再差分并存储。只记形状的指纹(maxDepth 3、maxKeys 20)让开销可忽略、负载值永不入日志。 2. 逐 listener 窗口归因(P2) — 注册时拦截 internal/listener(bail),包裹每个非 internal 的 listener。每次被包裹的调用都在*该 listener* 前后快照共享参数,从而把变更精确归因到执行它的插件 + fiber(confidence: "window")。框架内部事件永不包裹——无递归、无自噪音。
局限
- 只记形状的指纹:等长字符串互换(
'by-c'→'by-d')与原地数字修改对差分不可见。这是隐私与成本的既定取舍;值感知的deep模式列为未来工作。 - 同步路径时序:微任务恢复对同步
emit精确;对serial/parallel/waterfall的异步 listener,P2 的逐 listener 窗口包裹精确覆盖了这一缺口,分发级差分仍作为粗略总览。
与其他插件的关系
生态中的可观测性插件分层次:有的展示*插件往模型 prompt 里放了什么*(context 层),而 dsh-audit-log 展示*插件在事件数据流里改了什么*(运行时数据层)。两者互补——建议一起安装。
开发
npx @dsh-io/dsh-dev check # 校验 manifest、YAML、构建
npx @dsh-io/dsh-dev dev # 在 dsh web 下运行并监听文件变化测试(无需测试框架):
node --test tests/许可
MIT