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

150 lines
8.2 KiB
Markdown

# 第一版内存清洗核心如何工作
## 1. 它解决什么问题
清洗组件如果直接返回一整篇新 Markdown,调用方只能看到修改后的结果,很难确认它实际改了哪里。组件保存的
旧位置还可能在文本变化后误中另一段内容;同一批修改发生重叠时,按不同顺序执行也可能得到不同结果。
当前核心把“判断应该改什么”和“安全地执行修改”分开:组件只描述绑定当前文本的精确修改,公共执行器统一
验证并应用。这样可以在不引入真实清洗规则、文件读写或 Markdown parser 的情况下,先让组合与审计协议可运行。
已经实现的范围来自已批准的
[`0003-first-executable-core-architecture.md`](../design/0003-first-executable-core-architecture.md)。精确类名、字段和
函数签名以 [`src/mdpolish/`](../../src/mdpolish/) 中的代码和测试为准,本文不维护第二份 API 清单。
## 2. 当前数据流
```text
输入 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`](../../README.md#当前可用检查) 维护。
当前仍然没有:
- 论文、GovDoc、HTML 表格或其他真实清洗组件;
- 独立文档检查、人工建议或审核流程;
- Markdown parser、AST 或共享业务中间表示;
- 文件读写、CLI、批处理、项目 profile 格式和生产集成;
- 审计结果的长期存储或脱敏输出协议。
这些边界中的任何一项要进入实现,都需要先用新的 design 明确语义、代价和验收方式。