Files
mdpolish/research-wiki/explanation/local-experiment-artifacts.md
T

196 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地清洗实验如何保存 Markdown、审计和 diff
## 1. 它解决什么问题
内存流水线可以安全地产生 `TransformResult`,但进程结束后,评审者仍需要打开清洗后的完整 Markdown、查看总 diff
并追溯每条修改属于哪个组件、为什么修改、修改前后是什么。
当前本地实验层把这些结果保存到独立目录,同时继续保持三个边界:
- 组件和 `Pipeline` 仍然不读写文件;
- 输入文件永远不被覆盖;
- 只有 `success` 文档才产生正式的清洗后 Markdown。
已经实现的范围来自已批准的
[`0005-local-experiment-runner-and-artifacts.md`](../design/0005-local-experiment-runner-and-artifacts.md) 和
[`0007-local-markdown-reviewer.md`](../design/0007-local-markdown-reviewer.md)。
精确字段、校验和函数签名以 `src/mdpolish/` 中的代码与测试为准。
## 2. 保存与评审怎样解耦
```text
experiment.py
├── pipeline.py 只负责内存清洗
├── reporting.py 只负责 JSON、行列和 unified diff
└── artifact_store.py 只负责日期目录、权限和原子发布
已发布运行目录
reviewer/ 只通过发布后的文件做本地只读评审
```
- `experiment.py` 严格读取调用方显式列出的 UTF-8 Markdown,逐份调用同一个 `Pipeline`
- `reporting.py` 重放并校验 `Change` 的快照链,再生成机器可读审计和人可读 diff;
- `artifact_store.py` 不理解清洗规则,只把已经生成的字节写入私有临时目录,校验后一次性发布。
- 同仓库 `reviewer/server/` 只共用无文件 I/O 的 Python 快照重放模块,不导入组件或流水线;它读取 manifest、result、
成功输出和本机定位文件。
因此,新增组件不会改变文件层;调整目录布局不会影响清洗和报告;修改 JSON 或 diff 时也不需要碰流水线。
ClinDB 的 5 份论文、历史 arXiv 单组件组合和当前 first-batch 组合只存在于两个仓库内实验脚本,通用模块没有硬编码
论文名或业务组件。
## 3. 输入怎样保持原样
实验层先完成整批预检:
1. 验证日期、运行 ID、文档 ID 和来源标签;
2. 确认所有路径存在、是普通文件且没有重复;
3. 以二进制读取全部输入;
4. 使用严格 UTF-8 解码;
5. 比较原始字节 SHA-256 与核心 Markdown SHA-256。
读取过程不剔除 BOM,不规范化 Unicode,不转换 `\n``\r\n` 或文件末尾换行。全部文档运行结束后,
实验层再次读取每份输入并比较原始字节和哈希;任何变化都会阻止产物发布。
输入缺失、不是普通文件、不是有效 UTF-8 或清单冲突属于整批预检失败。这时不调用流水线,也不创建最终运行目录。
## 4. 状态怎样决定产物
每份文档独立运行,某一份发生核心错误不会阻止其他已经预检的文档继续产生结果。
| 文档状态 | `result.json` | `cleaned.md` | `changes.diff` |
| --- | --- | --- | --- |
| `success` | 有 | 有 | 有;零修改时为空文件 |
| `failed` | 有 | 无 | 无 |
| `unstable` | 有 | 无 | 无 |
`failed``unstable` 的审计仍保留已经实际发生的 `Change`、错误或残留候选,但不持久化
`partial_markdown`,避免半成品看起来像正式结果。
整批状态按 `failed``unstable``success` 的优先级汇总。即使其他文档有成功产物,只要一份失败,
`manifest.json` 就会把整批标为 `failed`
## 5. 产物怎样组织
```text
artifacts/
└── <YYYY-MM-DD>/
└── runs/
└── <run_id>/
├── manifest.json
├── review-locator.json
└── documents/
└── <document_id>/
├── result.json
├── cleaned.md
└── changes.diff
```
日期取实验启动时本机时区中的日历日期。运行 ID 由调用方显式提供;同一日期下已经存在同名目录时拒绝覆盖。
`manifest.json` 是整次运行的索引,记录:
- 开始、完成和到期时间;
- 本机日期及 UTC 偏移;
- mdpolish、Python、平台和 Git 状态;
- 实际组件顺序、版本和参数;
- 每份输入的来源标签、前后哈希、状态、修改数量和产物相对路径;
- 整批成功、失败、不稳定和修改数量。
每份 `result.json` 保存实际修改、错误与残留候选。每条修改包含组件、候选引用、理由、Python 字符范围、
1-based 行列、`before``after` 和批次前后哈希。完整成功文本只存在于 `cleaned.md`
`changes.diff` 是原始输入到最终成功输出的 unified diff,只用于人工查看。它不包含绝对路径或时间戳,
也不是修改重放的权威;机器审计仍以 `result.json` 为准。
`review-locator.json` 只记录本次运行目录、每份输入的绝对解析路径和输入哈希,供本地评审器重新找到完整原文。它不复制
原文,不替代 manifest 或 result,也不作为可移植运行身份。绝对路径可能泄露本机目录结构,因此该文件同样是本地敏感数据。
## 6. 为什么 reporter 要重放修改
第二个组件看到的是第一个组件修改后的快照,因此后续 `Change.span` 不一定对应最初输入。为了生成准确行列,
reporter 从输入开始,按组件批次重放修改:
1. 当前文本哈希必须等于该批次 `before_sha256`
2. 每条范围内的原文必须等于 `before`
3. 同一批次按核心相同的从后向前顺序应用;
4. 结果哈希必须等于 `after_sha256`
5. 全部批次完成后必须等于 `TransformResult.current_sha256`
任何一步不一致都说明内存结果、reporter 或调用方式违反契约,整次运行不会发布最终目录。行列只是方便人查看的
派生信息,修改权威仍是快照绑定的 Python 字符范围。
## 7. 文件怎样安全发布
artifact store 先在同一日期的 `runs/` 下建立本次专用临时目录。所有文件写入后都会重新读取校验,
成功 Markdown 还要再次核对输出 SHA-256,manifest 的身份、状态、计数和路径也必须与各文档审计及实际文件一致。
只有全部文件、清单和权限都通过,临时目录才会在 `runs/` 目录协作锁内重新检查目标,并原子重命名为最终运行 ID。
当前 Linux 本地实现使用:
- 目录权限 `0700`
- 文件权限 `0600`
- 已存在的目标目录拒绝覆盖;
- 写入中途失败时不发布最终目录。
这保证不会发布已知不完整的结果,但不承诺跨平台断电耐久性或网络文件系统语义。
## 8. 隐私和保留边界
`cleaned.md`、diff 和 JSON 审计都可能包含真实原文,因此整个 `artifacts/` 都是本地敏感数据:
- 受 Git 忽略;
- 不进入 Wiki、提交、推送或外部系统;
- 终端只显示状态、计数和目录;
- 默认保留 30 个日历日;
- manifest 记录 `retention_until`
- 第一版不自动删除,到期后仍需用户确认具体目录再清理。
当前只批准对 `data/md/` 中 5 份论文副本保存产物。仓库外 GovDoc 和其他真实数据没有因此获得输出授权。
同仓库只读页面的路径验证、组件快照重放和使用边界见
[`local-markdown-reviewer.md`](local-markdown-reviewer.md)。
## 9. 已完成的真实验证
2026-08-22 使用 `paper.arxiv_submission_stamp` `1.0.0` 对 5 份本地论文副本完成一次保存型实验:
| 项目 | 结果 |
| --- | --- |
| 运行 ID | `clindb-arxiv-stamp-artifacts-v1` |
| 输出位置 | `artifacts/2026-08-22/runs/clindb-arxiv-stamp-artifacts-v1/` |
| 文档状态 | 5/5 `success` |
| 实际修改 | `sim` 1 条、`springer` 1 条,其余 0 条 |
| 修改位置 | `sim` 1:1、`springer` 18:1 |
| 合法反向样例 | Springer 两处 `arXiv preprint arXiv:` 均保留 |
| 输出校验 | 5/5 `cleaned.md``current_sha256` 一致 |
| 输入只读 | 5/5 运行前后字节和哈希不变 |
| 权限 | 全部运行目录 `0700`,产物文件 `0600` |
实际运行方法见
[`run-local-clindb-arxiv-experiment.md`](../guides/run-local-clindb-arxiv-experiment.md)。
同日又使用 `design/0006` 的 8 组件流水线完成 ClinDB 第一批保存型实验:
| 项目 | 结果 |
| --- | --- |
| 运行 ID | `clindb-first-batch-v1` |
| 输出位置 | `artifacts/2026-08-22/runs/clindb-first-batch-v1/` |
| 文档状态 | 5/5 `success` |
| 实际修改 | dmp 47、ejhf 9、jama 79、sim 3、springer 17,共 155 条 |
| 第二次运行 | 5/5 `success`,合计 0 条修改 |
| 输出校验 | 5/5 `cleaned.md``current_sha256` 一致 |
| 输入只读 | 5/5 运行前后字节和哈希不变 |
| 内容反例 | JAMA Abstract 前内容不变;Springer 两条合法 arXiv 引用保留;图片引用文字不变 |
| 权限 | 运行目录 `0700`,产物文件 `0600` |
当前完整运行方法见
[`run-local-clindb-first-batch-experiment.md`](../guides/run-local-clindb-first-batch-experiment.md)。实验层的文件、
JSON、diff 和权限契约没有因组件增多而改变。
2026-08-23 又产生运行 `clindb-first-batch-reviewer-v1`,用于验证新定位文件和本地页面:5/5 文档为 `success`,合计
155 条修改,输入运行前后哈希不变。评审器 API 校验了 5 份原文、成功输出以及 8 个组件形成的 40 个阶段,所有文本哈希
均与审计一致。实际页面启动步骤见 [`review-local-cleaning-run.md`](../guides/review-local-cleaning-run.md)。