docs(登记): 哨兵的缺省语义进 §10;版本偏移的可见后果进差异表;"指针可以,断言不行"入册

pi 2026-09-14 的三点接续:

1. **"不存在的 id 当哨兵"这个手法本身是一次缺省语义决策**,我原先只把它当技巧用,
   没登记它的前提。已按他给的形状进 §10:**未知预设 id → 静默替换为 aurora | 方向:宽(静默)
   | 依据:产品决定(背景不该因未知 id 变空白)**。并写明它**不是"给一个安全的空值",
   而是"静默给别人的档"** —— 用户不会看到错误,但会看到别人的档。
   他指出的真实后果也记了(§10.2 + 计划文档差异表):**服务端/WebUI 先加第 7 档、
   鸿蒙还是旧构建时,旧端静默显示 aurora**,用户以为选的是新档;
   **这不是 bug,是批准过的缺省决策的可见后果** —— 不记,将来必被当 bug 报。
   他给出的可选取舍(兜底改成"不属于六档的显式层")我**不改**:代价是未知 id 显示空白,
   与"背景不该变空白"这条产品决定冲突。取舍与理由都写下来了。
   顺带记清一个连带事实:**用真实档当兜底,必然让那一档逃出行为判据**
   (aurora 丢分支不可观测)—— 这条边界已在判据注释里,不假装全覆盖。

2. **WebUI 侧不写"鸿蒙不消费"**(他否掉了我问的那件事):那会把**另一端的实现状态**
   写成**这一端的断言**,而它会过期。规则立成 §13:**注释里可以放指针,不要放断言**;
   断言留在"事实所属的那一端"(鸿蒙侧已写+已登记 §10),另一侧只放指针。
   与"按 id 联接不按相邻配对"、"'与 X 一致'先确认 X 侧有判据"同族。

3. **两处"只写不读"登记到同一处**:鸿蒙 `bgBlur` 与 WebUI `LEGACY_BACKUP_KEY` 都进 §10 表
   (§10.1 说明为什么必须同处:分居两处时审计容易只找到一处就以为找全了)。
   并**如实标注**:他建议的判据形态("登记为只写不读的字段,写侧必须能指出理由与批准人")
   目前只是**文档级约束**,还没有机器判据 —— 要变成"做错会红"得先有一张可扫的字段清册。
This commit is contained in:
2026-09-14 17:09:26 +08:00
parent b7f624bc45
commit 12a45af9ee
2 changed files with 43 additions and 2 deletions

View File

@ -350,8 +350,9 @@ pi 2026-09-14 提的形状:**不是无条件 fail-closed** —— 缺字段可
它的注释写明"省略或为空 = **不受限**",因为 17 个存量清单都没有这个字段,
若把空声明当最小权限,它们会全部静默失去 IO 注入与记忆读写。
| 字段 | 缺省语义 | 方向 | 谁批准 / 依据 |
| --- | --- | --- | --- |
| **未知的预设 id**(`layersFor`/`normalizePreset`) | **静默替换为 `aurora`** | **宽(静默)** | 产品决定:背景不该因未知 id 变成空白。**这是"静默给别人的档",不是"给一个安全的空值"** —— 用户不会看到错误,但会看到**别人的档** |
| 只写不读的字段:鸿蒙 `bgBlur` | **写进去、存下来、同步它,但没有任何消费点** | 无(当前无效果) | 核实于 2026-09-14:`Appearance.ts` clamp 存入、`AppearanceStore` 同步,**无页面读它**(材质档位是固定枚举)。⇒ 钉"px ↔ 档位映射表"会是**假判据**(钉一张不存在的表);将来开始消费时**必须**补映射判据 |
| 只写不读的字段:WebUI `LEGACY_BACKUP_KEY` | 删旧值前**另存一份**,没有任何代码读它 | 无(有意为之) | `backgroundStore.ts:200` 的注释写明目的(否则它会变成新的继承源)。**登记在此是为了审计能一次找全** |
| 邮件的 `permission_mode` | **不写、不改档**(不是写默认档) | 窄(fail-closed) | 本仓 2026-09-14:缺字段被 `\|\| 'workspace'` 兜成窄档,等于"一个 bug 以正常形态活着";守卫见 `plugins/*/lib/permission-mode.js` 的 `modeForStateWrite` |
| HomeAgent `plugin.json` 的 `sdk` | **内核不读**(字段只对人有效) | 无(不是语义,是文档) | 内核 `internal/plugin/manifest.go` 的结构体里没有该字段;`registry.go` 的 `loadOne` 只用 `NameZh/NameEn` |
| HomeAgent `plugin.json` 的 `capabilities` | **不受限** | 宽(fail-open) | 内核注释明写理由:17 个存量清单都没有它,空声明当最小权限会让它们**静默降级** |
@ -389,3 +390,42 @@ pi 2026-09-14 提的形状:**不是无条件 fail-closed** —— 缺字段可
在 `client/electron/src/components/CalendarView.tsx:204` 是**裸字面量**,
没有任何判据引用它 ⇒ 当时写"与 WebUI 一致"是把**当下的巧合**当契约。
处理:先标"待两边对齐",再让两边各自钉住数值本身。
### 10.1 两类"看起来被接住了、实际没有落点"的东西必须登记在**同一处**
上面表里最后两行是同一类形状:**值被搬运/保存,但没有落点**(一个是鸿蒙侧的 `bgBlur`,
一个是 WebUI 侧的 `LEGACY_BACKUP_KEY`)。分居两处的话,将来审计容易**只找到一处就以为找全了**
(pi 2026-09-14 指出)。所以两类都登记在这张表里:
1. **缺省语义**:缺了意味着什么、方向是宽还是窄、谁批准;
2. **只写不读**:谁在写、为什么还留着、什么条件下必须补判据。
**判据形态(想做但还没做,写在这里避免当成已完成)**:凡登记为"只写不读"的字段,
写侧必须能指出"有意为之"的理由与批准人 —— 这样"搬运了但没人读"就不会以**静默**形态长期存在。
现在这一条还只是**文档级**约束(人工核对),没有机器判据去强制它;
要变成"做错会红",得先有一张字段清册可扫。
### 10.2 版本偏移下的可见后果:旧客户端遇到"新档"会静默显示 aurora
跨端耦合的常态是**两端不同时上线**:服务端/WebUI 先加了第 7 档,而鸿蒙还是旧构建 ——
旧鸿蒙的 `layersFor` 认不出新 id,按上面的缺省语义**静默显示 aurora**,
于是"用户以为自己选的是新档"。**这不是 bug,是这条缺省决策的可见后果**(已按 pi 2026-09-14
的建议同时记进 `docs/HARMONY-ALIGN-PLAN.md` 的差异表)—— 不记,将来会被当 bug 报,
而查到最后发现"这是我们批准的"。
## 13. 注释里可以放**指针**,不要放**断言**(尤其是关于另一端实现状态的断言)
**规则**:不要在 A 端的源码注释里断言 **B 端**的实现状态。写"B 端不消费这个字段"这类句子,
就是把**别处的观测**当成了**本机的契约** —— 它会在 B 端变化的那天变成**假事实**,
而且没有人会回来改(写的人不在那条链上,读的人无法判断它是否还成立)。
**该放在哪**:
- **断言留在"事实所属的那一端"**:例如"鸿蒙不消费 `bg_blur`"是**鸿蒙侧**的事实,
所以它写在鸿蒙侧(`model/Appearance.ts` / `AppearanceStore.ets` 一带)+登记进 §10;
- 另一侧如果要留痕,只放**指针**:例如"该字段的消费方状态由 <那一端的登记处> 说明"。
指针不会过期(它指向的地方会自己更新),断言会。
**同族**:这条与"两张表各缺一半时必须按 id 联接,不能按相邻关系配对"、
"'与 X 一致'必须先确认 X 侧有判据钉住"是同一族 —— **比较/引用的一端必须是权威来源,
不是手边那份可能过期的副本。**