补丁提升:为一次代码修改自建的一套运输协议
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_id、bundle_sha256 |
Bundle 内容寻址身份;投影内容变化会产生不同身份 |
grant_id、grant_sha256 |
绑定签发写权限的精确 Grant 字节 |
executor_run_id |
绑定唯一执行实例,而不是接受任意同类 Executor 的输出 |
repository_identity |
指定源仓库身份,避免同名目录之间误投递 |
base_commit、base_tree |
指定候选修改基于哪个 Git 快照产生 |
changes、change_count、total_bytes |
有序、去重的逐文件修改及资源上限 |
created_at |
记录工件形成时间,不替代 Grant 的授权窗口 |
每个 changes[] 条目包含 path、operation 和 old / new 两个 fileState。fileState 又包含:
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_sha256、executor_run_id、bundle_id/bundle_sha256,并记录每项检查的 status 与 evidence_sha256。实现中的核心检查是:
- Bundle manifest 与每个 immutable blob 都能重新读取并通过哈希校验;
- 每个 path + operation 精确匹配 Grant 的 write allowlist;
- 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、
HEAD、HEAD^{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 为每个目标建立有序步骤,随后是 outcome 和 receipt。每个步骤只有 pending、completed、failed 三种状态,事务整体据此成为 in_progress、needs_recovery 或 completed。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:
- 重新加载同一个确定性
operation_id、decision/action 事件和 journal; - 验证恢复 lease 属于同一 stage,且 fence 不早于原 Grant;
- 对每个目标重新分类:精确等于 old、精确等于 new,或处于冲突状态;
- journal 已完成且目标为 new 的步骤直接确认;
- 未完成且目标仍为 old 的步骤可以继续;
- 目标既非 old 也非 new,或已完成步骤失去 new 状态时,fail closed 并进入人工对账。
这提供的是效果级幂等,不是声称文件系统只执行过一次写入。重复调用可能再次进行检查,但相同 operation 最终只能收敛到同一组 new 状态和同一份回执。已存在的 receipt 若字节完全一致可以复用;同一路径上出现不同条款则直接冲突。
系统也不会默认回滚已完成文件。自动回滚可能覆盖崩溃后用户产生的新修改,而且删除/创建的逆操作未必仍然安全。更可靠的策略是保留 journal 和精确 old/new 证据:能证明仍在原事务状态时继续,否则停止并人工决定继续、恢复还是重新生成 Grant。
PatchPromotionReceiptV1 是目标回读,不是发布证明
所有目标都达到 new 状态后,Host 才生成成功 outcome 和 PatchPromotionReceiptV1。回执包含:
- Grant、Bundle 与 verification receipt 的精确哈希;
- 确定性的
operation_id; - 每个 path 的
exists、sha256、mode目标回读; - 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。
相关文章
从 Workflow Harness 到轻量 Prompt:Codex 5.6 之后我删掉了什么
当模型的原生流程已经够强,Harness 会从效率放大器变成上下文负担。这次重写保留了安全边界,同时大幅精简 Prompt 和运行时。
写入授权与隔离工作区:能编辑文件,不等于被授权编辑这个文件
StageWriteGrantV1 将任务权限压缩为绑定版本、路径、操作与执行实例的一次性能力,并由隔离工作树和逐层回读共同实施。