补丁提升:为一次代码修改自建的一套运输协议

PatchBundleV1 将隔离环境中的候选文件状态封装为可验证工件,再由可信 Host 按日志步骤写入、回读并出具不可变回执。

本页目录 · 13 节

隔离 Executor 完成修改以后,源工作区仍然不应该直接信任它。Patch Promotion 解决的是一个本地信任边界:如何把低信任执行环境中的候选文件状态,受控地写入用户正在使用的源 worktree。

这里的 Promotion 不是部署、发布、Git merge,也不会自动 commit 或 push。它只完成一件事:在源仓库仍满足原授权前提时,把一组已验证的 old → new 文件状态写入目标路径,并留下足以回读、重试和恢复的证据。

为什么不把一段 diff 直接 apply

文本 diff 很适合人审查,却不是完整的写入合同。它通常不能单独回答:

  • 这段补丁来自哪个 Grant 和哪个 Executor run?
  • 修改前文件的精确内容、mode 和存在性是什么?
  • 新文件或二进制文件的权威字节存在哪里?
  • 删除操作怎样证明目标没有被别人替换?
  • 进程在处理若干文件后崩溃,重试时已经处理的文件该跳过还是重写?

因此运输单位不是「一段可能能应用的 diff」,而是 PatchBundleV1:一个绑定授权、仓库基线、逐文件双态和不可变 blob 的 manifest。Git 的 git apply --check 仍是理解 check/apply 分离的好参照,但这套机制以文件状态和内容哈希作为权限事实,不以 patch 上下文匹配作为最终依据。

PatchBundleV1:把候选结果冻结下来

Bundle 顶层字段绑定完整来源:

字段 含义
bundle_idbundle_sha256 Bundle 内容寻址身份;投影内容变化会产生不同身份
grant_idgrant_sha256 绑定签发写权限的精确 Grant 字节
executor_run_id 绑定唯一执行实例,而不是接受任意同类 Executor 的输出
repository_identity 指定源仓库身份,避免同名目录之间误投递
base_commitbase_tree 指定候选修改基于哪个 Git 快照产生
changeschange_counttotal_bytes 有序、去重的逐文件修改及资源上限
created_at 记录工件形成时间,不替代 Grant 的授权窗口

每个 changes[] 条目包含 pathoperationold / new 两个 fileStatefileState 又包含:

  • exists:这个状态下文件是否存在;
  • sha256:存在时的精确内容哈希;
  • mode:文件权限位;
  • bytes:内容长度,用于资源限制和完整性复核;
  • blob_ref:指向 audit 目录中不可变内容 blob 的路径与 SHA-256。

create、modify、delete 因而可以统一表示:

operation old new
create exists: false 包含新内容、mode 与 blob
modify 包含旧内容状态 包含新内容状态
delete 包含旧内容状态 exists: false

Bundle 构建器不会相信 Executor 自报的文件清单。它独立读取隔离 worktree 的 Git 状态、基线 blob、当前文件字节与 mode,再检查路径是否完全落在 Grant allowlist 内。所有 old/new blob 都被重新读取、哈希并封存;Bundle ID 绑定 Grant、run、base 与完整 changes。这样,后续步骤面对的是一个冻结工件,而不是仍在变化的工作目录。

验证回执证明什么,也不证明什么

提升前生成 PatchVerificationReceiptV1。它绑定 grant_id/grant_sha256executor_run_idbundle_id/bundle_sha256,并记录每项检查的 statusevidence_sha256。实现中的核心检查是:

  1. Bundle manifest 与每个 immutable blob 都能重新读取并通过哈希校验;
  2. 每个 path + operation 精确匹配 Grant 的 write allowlist;
  3. Bundle 的 old 状态精确匹配 Grant 的 target preconditions。

只有全部通过,回执状态才是 passed。回执自身也有投影哈希,已存在同一路径的回执必须与预期字节完全一致。

需要特别区分:这是一张完整性与授权验证回执,不是业务正确性证明。它能证明「这些字节来自这次受权执行,并且可以在这些前提下进入这些路径」,不能单独证明代码逻辑正确、测试覆盖充分或生产行为符合预期。单元测试、类型检查、构建和运行时验证属于更外层的 verification contract。

Host 提升协议

可信 Host 是唯一允许修改源 worktree 的执行者。完整路径如下:

sequenceDiagram
  participant E as Isolated Executor
  participant A as Immutable Audit Store
  participant H as Trusted Host
  participant R as Source Worktree

  E->>A: PatchBundleV1 + old/new blobs
  H->>A: 重读 Grant、Bundle、blobs
  H->>A: PatchVerificationReceiptV1
  H->>R: 锁定仓库并回读 branch/HEAD/tree/index
  loop 每个 change
    H->>R: old/new 分类
    H->>R: 原子 replace 或受检 unlink
    H->>R: 精确 new 状态回读
    H->>A: 更新 partial-commit journal
  end
  H->>A: outcome event + PromotionReceiptV1

1. 再次确认运行时权限

Host 会核对 StateV3 中的 workflow/stage/attempt/run/generation/resume epoch 与 Grant,要求当前 lease 及正确 fence,并验证 Grant 没有在事务开始前过期。源仓库身份由 repository root 与 Git common directory 共同派生,避免把补丁投递到另一份 checkout。

2. 锁定并复核目标仓库

提升使用 Git common directory 下的进程锁与文件锁串行化同一仓库的 promotion。锁文件以 no-follow 方式打开,并检查 regular-file、link count 与 inode,降低锁路径被替换的风险。

锁内再次读取:

  • 当前 branch、HEADHEAD^{tree}
  • Grant 的 base_commit/base_tree 与 Bundle 基线;
  • 每个目标的 index stage、blob hash 和 mode;
  • 工作区目标当前是否精确等于 Bundle old 或 new 状态。

任何一项变成第三种状态都属于 target drift。系统不会尝试三方合并,也不会拿新 HEAD 猜测如何重放旧 Bundle。

3. 为操作建立确定性身份与日志

operation_id 由 Grant hash、Bundle hash、verification receipt hash 和 repository identity 派生。同一组条款得到同一个 operation,不同条款无法借用旧日志。

状态中的 partial-commit journal 为每个目标建立有序步骤,随后是 outcomereceipt。每个步骤只有 pendingcompletedfailed 三种状态,事务整体据此成为 in_progressneeds_recoverycompleted。Event ledger 另外保存一条精确的 decision → action → outcome 因果链。

4. 每个文件都做旧态确认、原子落盘和新态回读

对 create/modify,Host 在目标同目录创建随机临时文件,写入 immutable blob、设置 mode、fsync,再确认目标仍是 old 状态,最后用 os.replace 原子替换并再次 fsync 父目录。对 delete,Host 保持目标描述符,反复核对 device/inode、owner、link count 与 old 状态,再执行 unlink

路径解析不是一次 resolve() 就结束。运行时保留仓库根到目标父目录的描述符链,禁止跟随 symlink,并在读、写、replace/unlink 前后验证父链与叶子身份。完成一个文件以后必须回读到精确 new 状态,才能把对应 journal step 标为 completed

需要注意:单个文件替换可以是原子的,多文件 Bundle 不是一个文件系统原子事务。partial-commit journal 提供的是可检测、可恢复的多步骤提交,不是假装所有文件在同一个瞬间变化。

部分应用后怎样恢复

假设 Bundle 含有多个文件,Host 在完成一部分后崩溃。恢复流程不会从头盲目 apply:

  1. 重新加载同一个确定性 operation_id、decision/action 事件和 journal;
  2. 验证恢复 lease 属于同一 stage,且 fence 不早于原 Grant;
  3. 对每个目标重新分类:精确等于 old、精确等于 new,或处于冲突状态;
  4. journal 已完成且目标为 new 的步骤直接确认;
  5. 未完成且目标仍为 old 的步骤可以继续;
  6. 目标既非 old 也非 new,或已完成步骤失去 new 状态时,fail closed 并进入人工对账。

这提供的是效果级幂等,不是声称文件系统只执行过一次写入。重复调用可能再次进行检查,但相同 operation 最终只能收敛到同一组 new 状态和同一份回执。已存在的 receipt 若字节完全一致可以复用;同一路径上出现不同条款则直接冲突。

系统也不会默认回滚已完成文件。自动回滚可能覆盖崩溃后用户产生的新修改,而且删除/创建的逆操作未必仍然安全。更可靠的策略是保留 journal 和精确 old/new 证据:能证明仍在原事务状态时继续,否则停止并人工决定继续、恢复还是重新生成 Grant。

PatchPromotionReceiptV1 是目标回读,不是发布证明

所有目标都达到 new 状态后,Host 才生成成功 outcome 和 PatchPromotionReceiptV1。回执包含:

  • Grant、Bundle 与 verification receipt 的精确哈希;
  • 确定性的 operation_id
  • 每个 path 的 existssha256mode 目标回读;
  • decision、action、outcome 三个 event ID;
  • promoted_at 与回执自身哈希。

它证明的是「源 worktree 在该时刻被回读为这组目标状态」。它不证明代码已 commit,不证明远端分支已收到提交,更不证明 CI、部署或生产 readback 成功。把这些层级混在一起,会把本地文件证据误报为交付证据。

常见失败模式

失败 检测点 处理
Bundle 被替换或 blob 内容变化 Bundle/receipt 投影哈希及 blob 重读 拒绝提升
路径或 operation 越权 Grant allowlist 与 Bundle changes 精确比较 拒绝整个 Bundle
用户在等待期间修改目标 old/new 分类、index 与 HEAD/tree 回读 停止,不覆盖用户状态
目标父目录被换成 symlink no-follow 描述符链与 inode 验证 停止并标记步骤失败
写入第 N 个文件时崩溃 partial-commit journal + 每目标回读 在同条款下恢复,或进入人工对账
客户端超时但 Host 实际完成 确定性 operation ID、existing receipt、target new 状态 回读事实,不重复创建另一条事务
功能测试失败 外层 verification/test 节点 不把完整性回执误当成功能验收

Trade-off 与适用边界

Patch Promotion 用 manifest、不可变 blob、两类回执、仓库锁、事件链和恢复日志,换取了低信任 Executor 与高价值源工作区之间的强边界。它特别适合并发执行、长任务、可中断进程、用户有重要未提交改动,或需要事后回答「谁依据什么授权写入了哪些字节」的场景。

代价是协议和实现都很重:文件越多,哈希、blob 存储和逐目标 fsync 越昂贵;路径、权限位、大小写和 Git index 都会成为兼容性表面;恢复代码必须覆盖比正常写入更多的状态。对于单执行者、低风险且可立即人工审查的修改,feature branch、平台 sandbox、git diff 与测试可能已经提供足够证据。选择标准不是机制看起来多严谨,而是它降低的预期损失是否超过持续维护和执行成本。

参考资料

  • Git: git-apply--check、应用边界及文本补丁语义,用于对照 check/apply 分离。
  • OpenAI: Harness engineering:把仓库知识、机械约束和 Agent 反馈回路组成可维护的工程环境。
  • SLSA Provenance v1.2:工件、生成过程和可验证元数据的供应链模型。本文只借用「内容与来源绑定」的设计类比,不声称本地 Bundle 符合 SLSA。
  • in-toto: Getting started:步骤产物与证明链的基本思想。本文的 receipt 是本地运行时合同,不等同于 in-toto attestation。

相关文章

在做类似的事情?

很乐意就分布式系统、交付流程和应用 AI 交换意见。

[email protected]