Bundle
dsh-audit-log
Runtime data-flow audit log for DeepSeek Harness: who mutated what, in what order — per-plugin, per-fiber attribution.
- Source
- ssdyg4444-sys
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 13 days ago
Readme
# dsh-audit-log
> **Who mutated my data flow?** — Runtime data-flow audit log for DeepSeek Harness plugins, with per-plugin, per-fiber attribution.
**English** | [简体中文](README.zh-CN.md)
`dsh-audit-log` answers the question every plugin ecosystem eventually asks: when a field in your session data
disappears or gets rewritten, **which plugin did it, in what order, and what exactly changed**?
It is a read-only observer: it never blocks, never rewrites, and never stores payload values — only structural
fingerprints (types, keys, lengths). Secrets never enter the log.
## Why
Cordis (the framework under DeepSeek Harness) shares the same `args` array across every listener of a
`waterfall` dispatch, and the `internal/get` / `internal/set` interception points are public. Any plugin can
silently rewrite data flowing through the system. With hundreds of plugins, "which plugin washed out my
field" is otherwise nearly impossible to answer.
`dsh-audit-log` makes it answerable:
- **Dispatch-level diff** — every audited dispatch snapshots argument shapes before and after, and records
the mutations in between.
- **Per-listener window attribution** — each listener is wrapped at registration; mutations are attributed
to the exact plugin + fiber that performed them (`confidence: "window"`), not a heuristic.
## Install
```bash
# into your web profile
cd ~/.dsh/profiles/web
pnpm add dsh-audit-log
```
Then add `dsh-audit-log` to `dsh.profile.bundles` in `package.json` (or use `dsh plugin --profile web add dsh-audit-log`),
and restart your dsh instance.
The plugin registers `ctx.auditLog` and starts recording immediately. No configuration required for basic use.
## Usage
Query the audit trail from any plugin:
```js
const records = await ctx.auditLog.query({
events: ['message/send'],
mutationsOnly: true, // only dispatches that changed something
fromSeq: 100,
limit: 50,
})
// Each mutation carries exact attribution (when attribute_by_window is on):
// { listenerIndex, package, fiber, confidence: 'window' }
const culprit = records[0].mutations[0].attribution?.package
// Raw per-listener windows:
const windows = ctx.auditLog.queryWindows({ event: 'message/send' })
```
### Record shape
```jsonc
{
"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" }
}]
}
```
## Configuration
Via the profile patch layer (`cordis.patch.yml`), or `$DSH_HOME/settings.yaml` namespace `audit-log`:
| Field | Default | Meaning |
|---|---|---|
| `enabled` | `true` | Global switch |
| `capacity` | `10000` | Ring-buffer size (oldest dropped first) |
| `mutations_only` | `false` | Only keep dispatches that mutated something |
| `attribute_by_window` | `true` | Per-listener attribution (wrap listeners) |
| `package_allowlist` | `[]` | Regex sources; empty audits all packages |
| `package_blocklist` | `[]` | Regex sources; excludes after allowlist |
| `events` | `[]` | Exact event names to audit; empty audits all |
| `event_allowlist` | `[]` | Regex sources for event names |
```yaml
- id: audit-log
config:
events: ["message/send", "before/*"]
package_blocklist: ["my-noisy-plugin"]
```
## How it works
```
dispatch → internal/dispatch (prepend) → fingerprint(args) → listeners run → fingerprint(args) → diff → store
```
1. **Dispatch-level diff (P0/P1)** — Cordis emits `internal/dispatch` synchronously *before* public listeners
run. The service mounts that one hook (`prepend: true, global: true`), snapshots argument shapes, lets the
dispatch proceed, then diffs and stores. Shape-only fingerprints (`maxDepth 3`, `maxKeys 20`) keep the
overhead negligible and payload values out of the log.
2. **Per-listener window attribution (P2)** — intercepts `internal/listener` (bail) at registration time and
wraps every non-internal listener. Each wrapped call snapshots the shared args before and after *that
listener*, so mutations are attributed to the exact plugin + fiber (`confidence: "window"`).
Framework-internal events are never wrapped — no recursion, no self-noise.
## Limitations
- **Shape-only fingerprints**: equal-length string swaps (`'by-c'` → `'by-d'`) and in-place number edits are
invisible to the diff. This is a deliberate privacy/cost trade-off; a value-aware `deep` mode is future work.
- **Sync-path timing**: the microtask resume is exact for synchronous `emit`; for async listeners in
`serial`/`parallel`/`waterfall`, the per-listener window wrapper (P2) covers the gap precisely, while the
dispatch-level diff remains a coarse overview.
## Relationship to other plugins
Observability tools in the ecosystem come in layers: some show *what plugins put into the model prompt*
(context layer), while `dsh-audit-log` shows *what plugins changed in the event data flow* (runtime data
layer). They are complementary — install both.
## Development
```bash
npx @dsh-io/dsh-dev check # validate manifest, YAML, build
npx @dsh-io/dsh-dev dev # run under dsh web with file watching
```
Tests (no framework needed):
```bash
node --test tests/
```
## License
MIT
Install
dsh plugin --profile web add github:ssdyg4444-sys/dsh-audit-log
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-audit-log from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.