Files
mdpolish/research-wiki/explanation/first-executable-core.md
T
Bepr4 3edeeaf30e 实现第一版内存清洗核心
落实不可变数据契约、组件基类、原子修改执行器与顺序流水线。补充稳定性复查、审计记录、测试和当前机制文档。
2026-08-22 01:03:03 +08:00

8.2 KiB

第一版内存清洗核心如何工作

1. 它解决什么问题

清洗组件如果直接返回一整篇新 Markdown,调用方只能看到修改后的结果,很难确认它实际改了哪里。组件保存的 旧位置还可能在文本变化后误中另一段内容;同一批修改发生重叠时,按不同顺序执行也可能得到不同结果。

当前核心把“判断应该改什么”和“安全地执行修改”分开:组件只描述绑定当前文本的精确修改,公共执行器统一 验证并应用。这样可以在不引入真实清洗规则、文件读写或 Markdown parser 的情况下,先让组合与审计协议可运行。

已经实现的范围来自已批准的 0003-first-executable-core-architecture.md。精确类名、字段和 函数签名以 src/mdpolish/ 中的代码和测试为准,本文不维护第二份 API 清单。

2. 当前数据流

输入 Markdown 字符串
        │
        ▼
带内容哈希的当前快照
        │
        ▼
组件提出精确修改 ──► 整批验证 ──► 整批应用 ──► 新快照
        │                                      │
        └──────── 按组件顺序重复 ◄──────────────┘
                                               │
                                               ▼
                                  最终只重新提议,不再应用
                                               │
                              ┌────────────────┼────────────────┐
                              ▼                ▼                ▼
                           success          unstable          failed

核心只有四层:

层次 当前职责 明确不负责
数据模型 保存快照、范围、候选修改、实际改动、错误和结果 业务规则和文件路径
组件基类 声明身份、版本、参数、适用边界并提出修改 应用修改和组织流水线
修改执行器 统一验证并原子应用一个组件批次 判断 Markdown 业务语义
流水线 排列组件、刷新快照、处理失败并做最终复查 读取文件、选择项目 profile

依赖保持单向:组件基类和修改执行器只依赖数据模型,流水线可以调用前三者,底层模块不反向调用流水线。

3. 为什么修改必须绑定快照

每个 Markdown 快照都带有根据完整字符串计算的 SHA-256。候选修改和其中每条文本编辑都必须指向这个哈希, 还要同时提供原文范围和该范围预期出现的文字。

执行时会再次检查:

  1. 哈希仍然对应当前快照;
  2. 范围没有越过字符串边界;
  3. 当前位置的文字与组件声明的预期原文完全一致;
  4. 替换后确实会改变内容。

任何一项不满足,当前组件的整个批次都不会执行。这使位置只在其产生时的快照内有效,不允许把旧候选修改悄悄 套到后来变化的 Markdown 上。

范围使用 Python 字符串下标,而不是 UTF-8 字节位置。核心保留输入的换行、Unicode 形式和末尾换行,不做隐式 规范化。

4. 组件为什么只提出自动修改

第一版组件只返回能够立即、唯一执行的候选修改。遇到不知道正确修法的截断、损坏表格、疑似幻觉或无法读取的 图片时,组件应忽略,不猜测修复,也不额外生成“仅检查”结果。

每个组件必须提供稳定标识、MAJOR.MINOR.PATCH 版本、可冻结的参数和非空适用边界。适用边界需要由组件作者说明 它处理什么结构、依赖哪些严格前置条件、明确排除什么。组件还必须满足确定、无副作用和幂等约束;不能读取文件、 网络、环境变量、当前时间或随机数。

最终复查能发现多个组件组合后仍会继续提出修改,但不能从有限输入证明一个组件对所有文本都幂等。因此,幂等性 既由最终复查保护当前运行,也必须由组件自己的针对性测试证明其适用范围内的行为。

独立检查和人工建议目前没有实现。以后只有出现明确消费者和闭环时,才通过新 design 增加平行接口,不在当前 组件结果中补可空字段或状态枚举。

5. 一个组件批次如何保证原子性

一个组件可以提出多个候选修改,每个候选修改又可以包含多个文本编辑。执行器先验证该组件本次提出的全部编辑, 只有整批通过才从后向前应用;任意一条失败,整批保持原样。这里的原子边界是“当前组件本次执行的全部修改”, 不是单独一条编辑。

当前冲突规则有意保守:

两项编辑的关系 结果
两个非空范围真正重叠 冲突
两个非空范围只相邻 允许
两次插入位于同一点 冲突
两次插入位于不同点 允许
插入点位于非空范围内部、起点或终点 冲突

如果业务动作需要替换一段文字并在边界追加内容,组件应把它表达成同一条替换,而不是依赖编辑执行顺序。

6. 流水线状态代表什么

流水线先对所有组件做元数据预检,避免运行到一半才发现重复标识或无效版本。之后每个组件只执行一次,后一个组件 只能读取前一个组件产生的新快照。清洗阶段出现异常、契约错误或编辑验证错误时会立即停止,且不再进行最终复查。

所有组件完成后,流水线让它们针对最终快照重新提出一次修改,但这一阶段只验证、不应用:

状态 含义 Markdown 字段
success 清洗和最终复查均完成,所选组件不再提出修改 只提供成功输出
unstable 清洗无错误,但最终复查仍有有效候选修改 只提供诊断用部分文本和残留候选
failed 清洗或最终复查发生错误 只提供诊断用部分文本和错误

最终复查是只读阶段,因此某个组件失败后仍会继续复查其余组件。错误与其他组件的有效残留修改可以同时保留,最终 状态以 failed 为准。流水线不会因为 unstable 自动开始第二轮。

success 只表示本次选中的自动清洗组件已经稳定,不表示文档没有截断、幻觉、表格损坏、图片断链或其他未实现 规则能够发现的问题。

7. 审计记录能回答什么

每条实际执行的文本编辑都会生成一条改动记录,说明:

  • 是哪个组件、哪个版本和流水线位置执行的;
  • 属于哪个候选修改,以及在该候选修改中的编辑序号;
  • 组件给出的修改理由;
  • 修改前范围、原文和替换内容;
  • 当前组件批次修改前后的快照哈希。

同一候选修改中的多条记录共享候选引用,同一组件批次中的所有记录共享批次前后哈希。记录只描述已经发生的修改; 验证失败或最终复查中没有执行的候选不会冒充实际改动。

这些内容当前只存在于内存返回值中。仓库没有 reporter、审计文件格式或日志持久化,调用方也不能默认把失败结果中 的部分文本写回原文件。

8. 当前验证和剩余边界

核心测试使用短小的假组件,不包含或复制真实文档。测试已经覆盖空文本、中文和组合 Unicode、插入/删除/替换、 范围冲突、过期哈希、批次原子性、组件连锁影响、错误阶段、审计关联和成功结果再次运行等行为。

实际可用的安装与验收命令、最近一次验证日期和结果只在根目录 README.md 维护。

当前仍然没有:

  • 论文、GovDoc、HTML 表格或其他真实清洗组件;
  • 独立文档检查、人工建议或审核流程;
  • Markdown parser、AST 或共享业务中间表示;
  • 文件读写、CLI、批处理、项目 profile 格式和生产集成;
  • 审计结果的长期存储或脱敏输出协议。

这些边界中的任何一项要进入实现,都需要先用新的 design 明确语义、代价和验收方式。