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

9.5 KiB
Raw Blame History

本地清洗实验如何保存 Markdown、审计和 diff

1. 它解决什么问题

内存流水线可以安全地产生 TransformResult,但进程结束后,评审者仍需要打开清洗后的完整 Markdown、查看总 diff, 并追溯每条修改属于哪个组件、为什么修改、修改前后是什么。

当前本地实验层把这些结果保存到独立目录,同时继续保持三个边界:

  • 组件和 Pipeline 仍然不读写文件;
  • 输入文件永远不被覆盖;
  • 只有 success 文档才产生正式的清洗后 Markdown。

已经实现的范围来自已批准的 0005-local-experiment-runner-and-artifacts.md0007-local-markdown-reviewer.md。 精确字段、校验和函数签名以 src/mdpolish/ 中的代码与测试为准。

2. 保存与评审怎样解耦

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

failedunstable 的审计仍保留已经实际发生的 Change、错误或残留候选,但不持久化 partial_markdown,避免半成品看起来像正式结果。

整批状态按 failedunstablesuccess 的优先级汇总。即使其他文档有成功产物,只要一份失败, manifest.json 就会把整批标为 failed

5. 产物怎样组织

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 行列、beforeafter 和批次前后哈希。完整成功文本只存在于 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

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.mdcurrent_sha256 一致
输入只读 5/5 运行前后字节和哈希不变
权限 全部运行目录 0700,产物文件 0600

实际运行方法见 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.mdcurrent_sha256 一致
输入只读 5/5 运行前后字节和哈希不变
内容反例 JAMA Abstract 前内容不变;Springer 两条合法 arXiv 引用保留;图片引用文字不变
权限 运行目录 0700,产物文件 0600

当前完整运行方法见 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