ADR-007 — 状态写入两步(candidate→confirmed)
状态 = accepted(owner 2026-05-31 全签 §2 D1–D5 主控拟案)。M1 状态化龙头决策,下游 Judgment Record/Watchlist/Profile 写入全依赖本 ADR。字段权威以 contracts/state-machines.yaml 为准。
§0 决策简述(拟)
FinBayes 的用户确认型长期状态资产(Judgment Record / Watchlist Object / Session 元数据更新)一律走两步写入(Dynamic Profile 是例外——静默写入,见 §6 修订):
任务执行产出 StateCandidate ──→ Candidate Buffer(暂存,不入 Confirmed Store)
│ 用户 confirm / edit-confirm
▼
Confirmed Store(对应资产对象)
│ reject / 超时
▼
丢弃(Rejected / Expired)
核心不变量:confirmed 状态只能由用户确认触发——系统永不替用户写 confirmed state(用户主权,战略不变量)。
§1 上下文
- 依据状态机:架构 §11.5 StateCandidate + §11.4 Judgment Record。候选类型→Confirmed 映射:
JudgmentCandidate→Judgment Record、WatchlistCandidate→Watchlist Object、ProfileSignalCandidate→画像字段、SessionRenameCandidate→Session 元数据。 - M0 现状:仅 Fin Object(created),单默认 Session,无两步写入。
- 为什么 M1 需要它:M1 目标 = 价值沉淀为可复用资产而非聊天文本(ADR-016)。两步写入是"资产"与"用户主权"的交汇点:没有它,要么不沉淀(无资产)、要么系统擅自落库(违用户主权)。
§2 决策点(✅ owner 2026-05-31 全部按主控拟案签定)
| # | 决策点 | 主控拟案 | 备选 |
|---|---|---|---|
| D1 | Candidate Buffer vs Confirmed Store 存储隔离 | 同 SQLite 不同表 + 不同写入路径(Candidate Buffer 表 / Confirmed Store 表;只有 confirm 动作能写后者) | 分库;同表加 status 列(否决:易误读为已确认) |
| D2 | 确认粒度 | 支持整候选确认 + 字段级编辑确认(ConfirmedEdited) | 仅整候选确认(弱,用户常想改一处) |
| D3 | Pending 过期 | 默认 7 天 Expired 自动清理,可配 | 永不过期(否决:Buffer 膨胀) |
| D4 | 写入 API 形态 | State API 单点:所有入口(CLI/TUI/Web)提交 StateAction,不直写 Confirmed Store(呼应 goal-execution §5) | 各入口直写(否决:逻辑复制 + 主权漏洞) |
| D5 | M1 walking 增量先做哪个候选类型 | JudgmentCandidate → Judgment Record(旗舰价值"判断沉淀",端到端证两步写入) | 先 Watchlist(更简单但价值弱) |
§3 边界与不变量(硬,不随实现变)
- I·用户主权:confirmed 写入必须由显式用户确认动作触发;无"系统自动确认"路径。例外:Dynamic Profile 不属于 confirmed-store 资产,走静默观察写入(§6 修订);其用户主权由"查看 / 修改 / 清空"(拉式)+ 仅在覆盖用户亲设偏好时才提示确认来保障,故 §I 的"无自动确认"不适用于画像。
- II·候选隔离:StateCandidate 不进 Confirmed Store;Buffer 与 Store 物理/路径隔离。
- III·不级联删:删除 Session 不删除其引用的 Judgment Record / Watchlist Object(§11 独立资产)。
- IV·审计:每步(候选生成 / confirm / edit-confirm / reject / expire)写审计 trail。
- V·暴露面:候选与 confirmed 对象的用户面暴露遵 MP-5 暴露面分层。
§4 M1 实施切片 + 验收信号(拟)
- 切片:State Management 子系统最小版 = Candidate Buffer + Confirmed Store(Judgment Record 表)+ State API(submit candidate / confirm / edit-confirm / reject)+ 过期清理 + 审计联动。
- 验收:① 任务产出 JudgmentCandidate 进 Buffer、不入 Store;② 用户 confirm 后才出现在 Judgment Record;③ edit-confirm 落用户编辑值;④ reject/expire 不留痕于 Store、留痕于审计;⑤ 删 Session 不删已确认 Judgment;⑥ 无任何"系统自动 confirm"路径(测试断言)。
§6 修订(2026-05-31 owner 决议:Dynamic Profile 静默写入 + 推迟 M2)
触发:M1-C6 实施时主控把 Dynamic Profile 也做成了"显式确认两步",owner 指出画像应静默处理、无需显式提醒与交互。核查发现签字文档自相矛盾:§0/§I 与产品定义 §3.5 把画像扫进"显式确认两步",但产品定义 §10.1 实为"持续观察静默构建 + 仅覆盖用户亲设偏好时才提示",且 m1-state-confirmation engineering-pack 早已写明"Dynamic Profile 持久化不实现、推迟 M2"。
决议(owner 2026-05-31 签):
- Dynamic Profile 模型 = 静默写入:系统持续观察直接写入画像 store,不走 candidate→confirmed、不弹候选、不打断用户。
- 用户主权 = 拉式 + 覆盖式确认:用户随时可查看 / 修改 / 清空画像(清空明示"协作上下文已重置");用户亲手修改的偏好优先于系统观察;仅当新观察要覆盖用户亲设的偏好时,系统才提示"是否重新调整"并等确认(此为画像唯一的确认场景,对应
ProfileSignalCandidate)。 - §I "无自动确认" 对画像不适用:画像非 confirmed-store 资产;§I 的硬不变量继续完整约束 Judgment Record / Watchlist Object / Session 元数据。
- 时机 = 推迟 M2:画像持久化按 m1-state-confirmation pack 原计划归 M2。M1-C6 已超前实现的
dynamic_profiles表 /ProfileCandidate确认路径 / 画像 API 从 M1 代码撤出(保留 Watchlist 两步,正确且在范围内);M2 再按本修订从头建为静默写入。 - 产品定义同步:§3.5"画像走候选→已确认两步契约"一句按本修订更正(画像静默;两步确认契约仅适用 Judgment/Watchlist)。
state-machines.yaml 的 ProfileSignalCandidate/ProfileCandidate 类型保留(供 §6.2 覆盖确认场景,M2 用),不删。
§5 关联
- 上位范围:ADR-016 M1=状态化 only。
- 画像静默 + 推迟 M2:m1-state-confirmation engineering-pack;产品定义 §3.5 / §10.1。
- 状态机事实源:contracts/state-machines.yaml、架构 §11.4 / §11.5。
- 存储:架构 §15 数据存储划分。
- 暴露面:MP-5。