二阶段提案合并的安全边界

解释计划批准、暂存提案、二次确认和服务端精确合并如何共同形成可审阅的 Agent 写入边界。

#type / synthesis #status / growing #tech / ai #discipline / knowledge-base #resource / mcp #resource / chatgpt

[!abstract] 速记结论 第一次批准的是“把方案固化成待审提案”,第二次批准的才是“把指定提案合并到正式知识库”。两次批准之间必须存在可检查的 proposal ID、目标、预览或 diff。

[!info] related notes

二阶段提案合并的安全边界

范围

本文解释为什么一次“可以”不足以同时授权提案生成和最终写入,以及三层控制如何协作。它不规定具体知识库的 frontmatter 或标签内容。

为什么需要放在一起理解

Agent 在规划时只掌握抽象意图,例如“补一篇详细的 ChatGPT 实践笔记”。当它把意图变成完整 Markdown 后,真实影响才变得可见:

  • 最终文件名是什么;
  • 是新建还是覆盖;
  • frontmatter 和标签是否正确;
  • 删除或替换了哪些段落;
  • 是否修改了错误的同名笔记;
  • 是否引入断链。

如果第一次批准直接触发最终写入,用户批准的是意图,不是具体变更。

状态模型

stateDiagram-v2
    [*] --> Investigating
    Investigating --> Planned: 给出方案
    Planned --> Staged: 第一次批准
    Staged --> Rejected: 用户拒绝或要求修改
    Staged --> Applying: 第二次批准 + 操作确认
    Applying --> Applied: 校验与写入成功
    Applying --> Staged: 校验失败
    Applied --> Archived: 归档并记录审计

两次批准批准的对象不同

检查点用户看到什么允许的副作用不允许
第一次批准文件角色、目标、标签、结构和链接方案写入 `inbox/` 提案修改正式 `z/`
第二次批准proposal ID、目标、完整预览或 diff合并该精确提案合并其他提案或任意路径
ChatGPT 操作确认App、工具名和即将执行的动作发起一次 `apply_proposal`保存为无条件永久批准

三层安全控制

Agent 与 Skill:行为层

负责让模型按正确顺序工作、展示证据并停下来。优势是能理解语义;弱点是可能被误解、遗漏或受到 prompt injection。

ChatGPT 审批:交互层

负责在外部写操作前显示确认卡。优势是用户可见;弱点是它只知道工具调用,不理解 Vault 的全部内部不变量。

MCP Server:权限层

负责不可绕过的机械约束:

  • 只接受服务端签发的 proposal ID;
  • proposal 内容哈希必须一致;
  • patch 的目标基线哈希必须一致;
  • 只允许目标位于 `z/*.md`;
  • 拒绝路径穿越和 symlink 逃逸;
  • 单次调用只消费一个提案;
  • 成功后归档并追加审计。

真正安全保证应落在这一层。

并发修改为什么必须拒绝

生成 patch 后,用户可能在 Obsidian 中手工修改目标。如果此时仍按旧基线覆盖,Agent 会静默丢失人工改动。

因此提案需要记录目标生成时的 hash。合并时:

  1. 重新读取当前目标;
  2. 计算当前 hash;
  3. 与提案的 base hash 比较;
  4. 不一致则拒绝;
  5. 重新搜索、读取并生成新提案。

拒绝比自动三方合并更保守,但适合知识库:内容语义冲突需要人或 Agent 重新理解,不应机械拼接。

为什么需要归档和审计

审批卡只证明用户当时点过确认,不能回答后续问题:

  • 哪个 proposal 被消费;
  • 写入了哪个目标;
  • 当时完整内容是什么;
  • 索引是否成功更新;
  • 是否重复执行。

归档保存提案证据,JSONL 保存可查询事件,Git 保存正式文件版本。三者分别承担“意图快照、操作记录、结果历史”。

适用边界

适合:

  • 知识库、文档、配置和内容管理;
  • 写入可延迟几秒或一轮对话;
  • 用户需要检查 diff;
  • 冲突时宁可拒绝也不覆盖。

不适合直接照搬:

  • 高频实时协作编辑;
  • 必须自动解决并发合并的系统;
  • 每次写入都可完全无损撤销且风险极低的临时数据;
  • 紧急自动响应且无法等待人的控制系统。

最小验收

  • 第一次批准后 `z/` hash 不变;
  • 提案能列出精确 ID、目标和预览;
  • 第二次批准前无法调用最终写入;
  • 修改提案文件后合并被拒绝;
  • 修改 patch 目标后合并被拒绝;
  • 路径穿越目标被拒绝;
  • 成功后提案只能消费一次;
  • 归档、审计和索引结果可验证。
创建于 2026/8/8 更新于 2026/8/8