tp/postmortem/2026-07-28-api-semantics-phase-1.md
Kinneyzhang 972b6d4e4c Complete text-property facade and managed lifecycle
Add canonical query semantics, managed metadata and transactions, overlay-aware lookup, reproducible benchmarks, and synchronized API documentation.
2026-07-28 22:42:55 +08:00

67 lines
3.9 KiB
Markdown
Raw Permalink 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.

# API 语义收敛阶段 1属性所有权必须先于便利重载
## 背景
仓库审计发现的错误并非彼此独立的条件遗漏。`tp-text` 属性扩散、层重定义残留、显式 nil 丢失、搜索 nil 分裂、隐藏层覆盖外部编辑,都来自同一个更深的问题:
> 调用者、内嵌字符串、普通 Emacs 属性、层定义和 managed stack 对同一属性键的所有权没有统一表达。
继续为每个入口增加特殊分支会扩大语义分裂,因此本阶段先冻结 `docs/API-SEMANTICS.md`,再在拥有状态的层修复。
## 决策
### 1. presence 是数据,不是真值
属性缺失与 present-nil 必须使用 `plist-member` 一类 presence-aware 判断区分。搜索中 VALUE 省略不能再借用 nil 表达,因而增加唯一哨兵 `tp-any-value`。选择唯一对象而不是某个普通符号,是为了避免与合法属性值冲突。
### 2. `tp-text` 的属性所有者是 interval
内嵌字符串的位置 0 不能代表整串。初次应用与响应式更新都按每个真实 property interval 计算,外部 props 在每段上覆盖内嵌 props显式 nil 也参与覆盖。
逐段算出待写属性后仍必须执行调用者选择的操作语义:`tp-set` 覆盖指定键,`tp-reset` 替换完整属性集合,`tp-add` 合并 face-family 和嵌套 plist。实现中一度让“属性已逐段应用”的返回标志绕过了这一步导致文本内容相同时 reset 留下旧键、add 覆盖而不合并。最终把 operation 传到唯一的逐段写入点,并为字符串/缓冲区和 `tp-text nil` 初始化路径加入回归断言。
### 3. 层重定义必须拿到 old 与 new
只传 layer name 会让渲染器无法判断哪些键应删除。定义入口在替换注册表前保存 old props渲染器随后执行
1. 仅删除仍等于旧值的旧层键;
2. 保留无关键和已经被外部改写的值;
3. 写入新定义;
4. 同步 direct render 与 buried/hidden stack entry。
这比完整 `set-text-properties` 更安全,因为后者会错误取得整个区间的所有权。
### 4. 隐藏层冲突默认失败
隐藏层存在时,直接属性只是 managed stack 的渲染缓存。外部直接写入无法可靠归属到某一层。三个候选方案中:
- 静默覆盖会丢用户数据;
- 自动收编为匿名层会凭空改变 stack 结构;
- 明确失败能保留双方状态并暴露所有权冲突。
因此选择 `tp-layer-conflict`,并保证在 managed write 前检查。
### 5. NOERROR 只处理可预期的解析失败
增加 `tp-unresolved-layer`NOERROR 仅捕获这一类型。层 body、compute、transform 和内部不变量错误不属于“未找到”,必须传播。
### 6. 业务计算与 observer 分开
transform/compute 决定渲染结果失败后继续会产生貌似有效的陈旧输出因此错误传播。watcher 是副作用观察者,单个失败不应阻断 managed update错误以结构化 plist 记录在 `tp-reactive-observer-errors`,同时保留用户可见消息。
## 有意延期
参数化 mounted layer entry 没有保存调用实参,重定义时无法重新求值。为避免猜测参数或引入兼容包装,本阶段明确不自动刷新参数化实例。后续应让 managed entry 保存参数和定义版本,再设计迁移。
字符串/缓冲区搜索返回结构、复制/原地修改策略和 stack mutator 的 run-count 返回值也仍有历史差异;它们属于 canonical façade 阶段,不能混入正确性补丁。
## 验证标准
本阶段以以下证据作为停止条件:
- 每个确认问题先有失败回归测试;
- 聚焦测试覆盖字符串/缓冲区、present-nil、静态/响应式重定义和 hidden conflict
- warning-as-error 字节编译通过;
- 全量 ERT、固定种子乱序 ERT 与 README doctest 通过;
- 中英文 README、docstring、架构文档和 changelog 与规范同步。