# 🛡️ dsh-evidence
**为 DSH 文件快照提供可复核的本地清单与哈希链收据**
*SHA256 清单 • 逻辑哈希链收据 • 时效性判定 (`validUntil`)*
[](https://github.com/huangjua)
[](#)
[](LICENSE)
[核心亮点](#-为什么需要审计存证包) • [快速上手](#-快速上手) • [DSH 效率套件](#-dsh-agent-效率套件) • [工具列表](#-深度参考与架构) • [English](README.md)
---
### 💡 为什么需要审计存证包?
“测试全部通过”等任务结论需要可以复核的产物。记录文件快照后,审阅者能够比较当前文件与记录哈希,并查看关联的 claim 和本地收据链。本插件不观察或认证生成这些文件的命令。
**`dsh-evidence` 记录文件快照并检查本地一致性:**
- ⛓️ **哈希链收据**:每次 create/add 扩展逻辑哈希链,校验内容哈希、链结构及清单绑定关系。
- 📦 **复制快照 (`copy=true`)**:v3 保存包内 `files/...` 相对路径。移动整个包后校验当前包内副本;`source` 仅记录来源,不替代缺失副本。
- ⏱️ **时效性判定 (`validUntil`)**:有效期与完整性分开表达。`stale` 表示内容一致但修改时间与记录不同,不表示有效期已过。
- 🤝 **能力边界声明**:明确本地一致性检查的范围,以及不能独立证明执行真实性、作者身份或抵抗整包重写的限制。
---
## 🚀 快速上手
### 安装
```bash
# 在 DSH 插件环境中注入
dev_inject_plugin @dsh-external/dsh-evidence
```
### 典型使用
1. **创建证据包**:`evidence_create name="bench-2026" description="性能审计"`
2. **添加快照与断言**:`evidence_add name="bench-2026" paths="E:\\project\\bench.json|E:\\project\\verify.py" claim="供审阅的基准测试输出" validUntil="2026-12-31"`
3. **一键复核**:`evidence_verify name="bench-2026"`
### 输入路径与固定证据根目录
相对 `paths` 按调用会话所属的项目目录解析,目录来自实际宿主已核对的 `ToolRunContext.agent.session.header.cwd`。该值缺失、无效或不是绝对目录时返回 `RELATIVE_PATH_REQUIRES_CWD`,此时应传完整绝对输入路径。程序不会回退到插件启动目录,也不使用 `process.chdir()`。Windows 的 `C:file.txt` 盘符相对路径及 `\file.txt` 根相对路径会因含义不完整被拒绝,应使用 `C:\project\file.txt` 或完整 UNC 路径。
证据根目录另有固定规则:配置的 `evidenceRoot` 在插件 apply 时解析一次;相对配置值和每次调用的相对 `root` 都基于当时捕获的进程目录,不会随项目会话改变。绝对 `root` 可明确选择另一存储目录。未配置时使用 `DSH_HOME/evidence`,未设置 `DSH_HOME` 时使用 `%USERPROFILE%/.dsh/evidence`。建议以绝对配置路径指定独立的证据存储位置。
### 完整性、事务与兼容行为
v2、v3 必须具有非空、合法且绑定当前清单的 `receipt.jsonl`;缺失/空收据返回 `FAIL` 与 `RECEIPT_MISSING` / `RECEIPT_EMPTY`。普通追加在暂存前拒绝旧坏链和绑定异常。真正 v1 无收据包兼容读取,明确返回 `UNVERIFIED_LEGACY` 和证明力限制;普通追加只读,离线迁移保留原包。
工具失败适配宿主 `HarnessError` 契约,真实工具结果保留稳定错误码;有明确写入结果时,错误消息同时包含 `commitState`、`transactionId` 及锁释放失败信息。取消在提交点后到达时完成发布或留下可查询的恢复状态。已完成写入会返回 `COMMITTED_AFTER_CANCEL`,明确 `COMMITTED` 与事务 ID;更晚的宿主取消仍可能返回 `ABORTED`,文本会提示实际已提交状态。取消后先调用 `evidence_verify` / `evidence_show` 查询当前版本,勿直接重试;仅凭取消结果不能推断本次写入未提交。
`status` 表达完整性,`freshness=CURRENT|EXPIRED` 单独表达可读清单中记录的有效期,包括缺失或损坏文件的有效期。内容一致但过期的文件可以通过完整性校验。
写入在 `.transaction/` 暂存完整新清单、新收据链及副本;原子发布 `.transaction/COMMITTED` 是唯一提交点。标记前失败/取消保持旧发布内容,标记后按协议恢复完整新版本。相关错误包含稳定码以及 `commitState`、`transactionId`;用 `evidence_verify` 恢复和查询未完成事务。两个正式元数据文件分别发布,属于可恢复事务。本协议验证了进程骤停和注入写入故障,未验证断电持久性。
校验全程持锁,大文件校验会延迟追加;等待和哈希支持取消。活跃持有者不会因锁龄超过 10 秒而被抢占。接管和释放都在同一短门锁内检查,释放必须匹配 PID/token;仅 `ESRCH` 可确认持有 PID 已退出。PID 复用、旧锁、未知锁或遗留 `.lock.gate` 保守停止。校验超时返回 `status=BUSY`、`receiptChain=NOT_CHECKED`、`freshness=UNKNOWN`,不作完整性结论。
普通持有进程退出后,下一次调用可接管主锁并恢复事务。进程在短门锁内骤停时,需离线处理:停止所有写入进程,保留锁与事务现场,确认无人持有后人工处理可证明遗留的门锁;程序不会按年龄删除。不要混跑新旧锁协议。真实桌面端验收是独立的部署检查,模拟工具调用不能替代该结论。
### 目录扫描、限额与取消
`evidence_add` 异步扫描,默认不跟随文件链接或目录链接。跳过的链接、重复物理文件、当前证据包及其别名均报告为主动排除。损坏链接/junction、目录读取失败、属性读取失败或不支持的输入类型返回 `SCAN_INCOMPLETE`,不会静默变成部分扫描成功。当前包整体排除;其他可识别证据包目录中的 `.lock`、`.lock.gate`、`.transaction` 和指定事务清理目录也排除。可识别包目录外同名的正常输入仍会纳入。
默认 `maxFiles=10000`、`maxTotalBytes=10737418240`(10 GiB),自定义值必须是正安全整数。超限返回 `SCAN_LIMIT_EXCEEDED`,不截断或提交本次扫描。空路径或只有分隔符返回 `INPUT_EMPTY`;空目录或全部主动排除返回 `SCAN_EMPTY`。追加成功返回 `scan.status=COMPLETE`、文件数和总大小、最多 100 条排除路径,以及 `excludedTotal`、`exclusionsTruncated`。COMPLETE 仅说明包含上述排除规则的扫描范围已完成。
add/verify 中遍历、复制与哈希按顺序异步执行,并发上限为 1,文件读写块大小为 256 KiB。宿主取消信号传入等锁、扫描、复制、哈希和校验;操作 Promise 结束前相关句柄关闭。事务提交点前取消保留旧发布版本,提交点后按协议完成或报告恢复要求。读取前后以及追加提交前检查文件身份与属性;观察到变化时返回 `FILE_CHANGED`。这些检查不能为其他进程正在修改的外部文件提供强一致快照保证。只写元数据的 create 和离线迁移命令仍使用独立的同步事务发布路径;离线命令也保留同步复制。
### 有限结果与问题定位
`evidence_verify detailLimit=20` 返回 missing、corrupt、stale、expired、errors 的完整 `counts`,路径数组及 `issues` 受限,issues 含路径、错误码与原因。`issueTotal`、`issuesRemaining` 明确剩余条数;同一文件可对应多条问题。文本显示相同完整计数和剩余条数。`detailLimit` 接受 1 至 100 的整数。`status=PASS` 表达当前完整性,`freshness=EXPIRED` 表示记录的有效期已过;BUSY 或无法读取清单等情况返回 `freshness=UNKNOWN`。`stale` 表示哈希一致但修改时间与记录不同。
`evidence_list` 接受 `offset=0`、`limit=20`(最多 100),返回 `total`、`returned`、`remaining`、`nextOffset`,按名称排序。损坏、缺清单和忙碌的包目录保留,带状态及诊断。`READABLE` 只表示清单可读取;文件和收据完整性应调用 verify。query 在展示截断前搜索完整名称和描述。每次分页反映当次目录状态,并发新增可能影响后续页。
`evidence_show` 使用相同文件分页,收据另用 `receiptOffset=0`、`receiptLimit=10`(最多 100),返回 `page` 和 `receiptPage`,查看合法本地收据链的有限记录。收据损坏或不可用时给出原因。返回的描述、claim、source、收据 note 转义控制字符并限制为 500 个展示字符;收据 payload 路径数组最多展示 20 条并给出原始总数。此有限视图省略 metadata 和未识别扩展字段。`presentation` 明确省略与展示投影规则:清单和收据摘要保留原值供参考,但不能用分页、转义和截断后的对象重算原始摘要,应读取原文件复核。浏览不改变原文件,也不能证明内容真实性或命令执行。
`stale` 提示容许毫秒元数据与文件系统时间接口转换时最多 1 微秒的舍入差异。扫描、复制、哈希期间的变化检查仍比较精确纳秒属性及文件身份。
### v3 路径与旧格式兼容
v3 的 `storage="external"` 使用绝对键并校验当前外部文件;`storage="copy"` 使用规范的 `files/...` 相对键并校验当前包内副本。包内路径拒绝 `..`、盘符/UNC、设备名、ADS、链接和 junction;同名文件按大小写不敏感的 `-2`、`-3` 后缀命名。边界检查针对观察到的文件系统状态,不承诺抵抗另一个恶意进程持续替换路径。
v1/v2 外部引用保留兼容读取。旧副本仅能从可识别的 `<包名>/files/<文件名>` 映射到当前包内文件;不确定时返回 `MIGRATION_REQUIRED`。本地副本缺失仍失败,即使旧位置或原始来源存在也不读取替代。v2 外部追加保持 v2,复制追加需先离线迁移至新的 v3 包。
### 离线迁移
在候选插件目录运行,试迁移使用隔离副本:
```powershell
node scripts/migrate-bundle.mjs --root 'E:\evidence' --name 'old-bundle' --preflight
node scripts/migrate-bundle.mjs --root 'E:\evidence' --name 'old-bundle'
# 可选:--destination-root 'E:\new-evidence' --destination-name 'reviewed-v3'
```
默认生成同级 `old-bundle-migrated-v3`,已有目标不会覆盖;原包清单、收据和正文保留。预检拒绝坏链、绑定异常、缺失副本、不确定映射和未完成源事务。迁移全程持源锁,将实际复制字节与旧记录哈希比对,在 `metadata.migration` 记录来源元数据摘要和证明力限制。新收据链从本次迁移开始,不继承或伪装旧历史。来自 v1 的 `UNVERIFIED_LEGACY` 限制会留在来源记录中,即使新包当前一致性校验 PASS。提交前失败可能留下空目标预留目录;重试可选新名称,或离线检查该隔离残留。迁移不能用来修补损坏证据。
---
## 🧩 DSH Agent 效率套件
本插件是 **DSH Agent 开发者效率套件** 的核心成员 —— 4 个插件无硬依赖,组合使用实现完整工程闭环:
```mermaid
flowchart LR
M["🧠 dsh-local-memory
(1. 跨会话记住规则与偏好)"] --> E["⚡ dsh-context-economy
(2. 省 80%+ Token 读代码)"]
E --> A["🛡️ dsh-evidence
(3. 任务执行与交付存证)"]
A --> S["🔍 dsh-session-index
(4. 中文会话检索与书签)"]
S --> M
style M fill:#e8f4fd,stroke:#2b7de9,stroke-width:2px
style E fill:#eef9f2,stroke:#1e8e3e,stroke-width:2px
style A fill:#fef7e0,stroke:#f29900,stroke-width:2px
style S fill:#f3e8fd,stroke:#8430ce,stroke-width:2px
```
| 插件 | 套件定位 | 与审计存证层的协作 |
|---|---|---|
| 🛡️ **[dsh-evidence](https://github.com/huangjua/dsh-evidence)** | **审计存证层** (当前) | 记录文件哈希、本地收据与待审阅的 claim。 |
| 🧠 **[dsh-local-memory](https://github.com/huangjua/dsh-local-memory)** | **本地记忆层** | 可将记忆中的决策关联到已经审阅的证据包。 |
| ⚡ **[dsh-context-economy](https://github.com/huangjua/dsh-context-economy)** | **上下文经济层** | 可将基准测试的输出文件登记为证据快照。 |
| 🔍 **[dsh-session-index](https://github.com/huangjua/dsh-session-index)** | **会话历史检索** | 通过会话检索快速定位生成证据包的历史对话现场。 |
---
## 📖 深度参考与架构
🛠️ 5 个 evidence_* 工具列表
| 工具 | 作用 |
|---|---|
| `evidence_create` | 创建证据包目录、`evidence.json` 清单与 `receipt.jsonl` 哈希链收据 |
| `evidence_add` | 添加文件/目录快照(SHA256/size/mtime),可选 `claim`、`validUntil` 与 `copy` |
| `evidence_verify` | 校验文件哈希、收据一致性与有效期;返回有限条异常路径和完整计数 |
| `evidence_list` | 分页列出证据包目录,保留损坏或忙碌条目及原因 |
| `evidence_show` | 分页查看清单条目,另行分页查看本地收据记录 |
📁 证据包文件结构
```text
~/.dsh/evidence//
evidence.json # 清单(含 manifestHash 自完整性)
receipt.jsonl # 逻辑追加式哈希链收据
files/ # copy=true 时的独立副本
.transaction/ # 未完成事务;发布后清理
```
⚖️ 诚实能力声明
- **能检查**:当前文件与记录哈希、清单自哈希、收据内部链和清单绑定的一致性。`claim`、`description` 和收据时间戳属于本地记录的声明。
- **不能独立证明**:命令真实执行、内容真实性、作者身份、完整历史后缀删除或抵抗整个包重写。能替换全部本地文件的人可以重新计算一个内部一致的包。本地哈希不是数字签名,也不是可信的外部时间戳服务。
🧪 构建与测试
```bash
node scripts/build.mjs # 编译 src → lib
npm run typecheck
npm test # 运行测试套件
npm run pack:check # 打包检查
```
---