Skip to content

fix(browser): Windows API 浏览器结构兼容加固与可验证恢复 - #2378

Merged
BigPizzaV3 merged 3 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/browser-structural-compat-20261002
Oct 2, 2026
Merged

BigPizzaV3 merged 3 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/browser-structural-compat-20261002

Conversation

@Yuimi-chaya

@Yuimi-chaya Yuimi-chaya commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

概要

Refs #2173, #2294

加固 Windows API 模式下原生 Edge / Chrome 请求标识兼容,在 main 的未知运行时结构通道上增加词法绑定检查、组件事务守卫和可重算恢复。当前 HEAD 为 cea82591,基于 89615978,已包含 04ab629 和浏览器连接识别状态展示。

兼容与修改范围

  • 保留 RuntimeContract::for_manifest、FileCheck::Structural、adaptive 状态和 0.0.11 版本下限。已知 0.0.11 / 0.0.24 指纹继续作为快路径;未知运行时先经过上游结构检查,再解析实际回调绑定。
  • 真实 CUA 0.0.27 的 manifest 使用 runtime_archive_version/runtime_archive_name,没有 package 风格的 name/version。现在兼容两种格式,校验归档版本与文件名一致性,并保留最低版本检查。
  • 删除未知运行时的入口文件硬哈希准入,改验 @oai/cua-repl 的词法绑定和 awaited launch() 签名。入口注释、格式及诊断变化不需要登记新哈希。
  • 会话请求方法的已知 AST 摘要仅作快路径。未知形状通过参数绑定、稳定会话参数、awaited 策略调用、请求头决策数据流和请求转发关系校验。允许计数插桩、内部方法重命名和无关命令排除项变化;拒绝参数重绑定、请求头被覆盖、歧义及回调关系变化。元数据与策略回调仍检查其支持的语义契约,不承诺任意协议变化自动兼容。
  • 仅替换选中的策略实参并追加原有 helper,保留其余服务字节。没有修改 helper、worker 身份认证或浏览器后端。
  • 未知运行时的 Node / worker 检查 PE x64 格式,校验浏览器包导出;现算组件哈希用于修改前的精确事务守卫,不作为新增版本白名单。

恢复与检查器

  • 保留 main 的 schema-1 恢复;未知运行时使用 schema 2,记录字节位置、绑定及请求方法审计摘要。恢复重算最小变换并要求与候选逐字节相等,同时校验原件与候选哈希。
  • 恢复不需要启动 Node 或保留 descriptor,不复活删除的缓存、不覆盖外部修改,并保留原时间戳与事务锁。
  • 检查器隐藏运行、清理继承环境、禁用 addons、限制 V8 old-space 为 256 MiB,以 20 秒进程截止时间终止异常检查。stdin 使用临时常规文件,消除阻塞写入线程的退出等待;子进程终止并回收后删除输入文件。候选仍受 32 MiB 恢复上限约束。
  • 明确 Node 是信任根:PE 格式检查不证明发布者身份,也不能防止检查前已经发生的同用户恶意替换。事务守卫防止检查后的组件漂移,不是同用户攻击的安全边界。

CI 与依赖

  • Windows、macOS x64、macOS arm64 构建均加入 assemble-native-browser-inspector.mjs --check,验证完整生成 bundle。
  • CI 使用 npm ci 安装锁定依赖。原来的 npm install --package-lock=false 会忽略传递依赖的精确版本,与完整 bundle 的可复现检查冲突,导致首次构建三平台均出现 Inspector bundle drift;独立提交 cea82591 修正安装方式,保留严格检查。
  • Babel parser / traverse 和 esbuild 使用显式精确 devDependency,均复用现有依赖版本;没有新增依赖树中的包。组装脚本说明版本与审计摘要、bundle 可复现性的关系。
  • lockfile 的根版本号修正独立为 d49bd549,功能提交为 7173788d。
  • 新增公开的一方服务 specimen。CI 的 Node 22 仅解析 specimen;Rust 使用合成 manifest、假 PE 和 package fixture 直接覆盖检测、组件守卫与 schema-2 恢复。假 PE 与原生服务均不执行。
  • 私有真实运行时测试保留为额外验证,不再是新检测路径的唯一覆盖。

验证

  • cargo test --locked --offline --workspace -j 2 --no-fail-fast:1724 passed,0 failed,8 ignored,55 个结果组。
  • 真实 CUA 0.0.27 临时副本事务测试:1 passed,包括重复应用、时间戳恢复、组件漂移拒绝和 Node / descriptor 缺失后的恢复。
  • 真实 0.0.11 / 0.0.24 / 0.0.27 原始服务的检查器定向测试:6 passed,0 skipped。
  • 默认前端测试:336 passed,0 failed,1 skipped;跳过项是需额外指定本地原始服务路径的真实夹具测试,已另行运行上述定向验证。
  • TypeScript、Vite 生产构建、两种组装产物检查和 diff 检查通过。
  • 全新隔离依赖目录的 npm ci --offline、完整 bundle 检查、336 项前端测试、TypeScript 与 Vite 构建再次通过;没有替换现有共享依赖目录。

旧提交 932abca3 曾完成 Windows Release / setup / ZIP 编译及用户异机验收;该历史结果不等于当前整合版本已真人验收。当前整合版本未执行真实应用安装、关闭、重启或替换,未重新生成发布安装包;新 CI、当前版本真人验收和 macOS 运行验证仍需分别确认。

使用边界

实验选项默认关闭;不伪造官方登录,不修改 worker 认证,不绕过站点限制或原生操作审批,不分发原生运行时。Mac 浏览器适配与独立解锁器未修改。

旧版 Codex++ / 独立解锁器 v0.1.0 不理解 schema 2,降级前须用兼容版本还原。后续 helper 或插入格式变化须保留或版本化 schema-2 重算算法,以免旧日志无法恢复。

扩展可能保留请求标识状态,还原服务不等于清除该状态;prepared 不等于浏览器首次使用已验收。

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢这个改动,也感谢你在描述里主动说明「验收版本基于 09a9f1c,main 随后合入了 04ab6290,两套实现有重叠区域需协调」——这个透明度很有帮助。

技术增量是真实存在的(下面 2.5 列的三点 main 确实没有),但当前提交相对 main 是一次替换式改动而不是叠加式改动,会把 main 刚建立的降级通道收窄回 fail-closed。建议要求修改后再合。

一、与 main 04ab6290 的关系:不是重复,但方向有回退

1.1 FileCheck::Structural 降级通道被整体旁路(核心问题)

main 的签名收的是完整 manifest 字节:

// main, native_browser.rs:136
fn for_manifest(manifest: &[u8], runtime: &Path, service_sha: String) -> Result<Self>

内部解析 JSON、校验结构不变量,未知版本走 FileCheck::Structural 降级契约——这就是 04ab6290 为 issue #2294 建立的通道。

本 PR 改成只收哈希字符串:

// 本 PR, native_browser.rs:95-102
fn for_manifest(hash: &str) -> Result<Self> {
    match hash {
        "ba3691b0717b6df8064c3841a75c784e8af9633c7b47f2fdb56d8de099efe6fc" => Ok(Self::pinned()),
        CURRENT_MANIFEST_SHA => Ok(Self::current()),
        _ => Err(anyhow::anyhow!(...)),
    }
}

调用点(native_browser.rs:684-687):

Some(match RuntimeContract::for_manifest(&sha(&manifest)) {
    Ok(known) => known,
    Err(_) => structural::detect(paths, &key)?,   // ← 未知版本必然走这里
})

参数是 &sha(&manifest),所以只要 manifest 不是那两个已知哈希就必然 Err → 每次都转向 structural::detect。main 的 Structural 分支在未知版本上变成不可达代码。 这不是「再叠一层更严的校验」,是把降级通道替换成了本 PR 自己那套准入面。

1.2 ENTRY_SHA 把哈希白名单重新引入(这是必须拦的点)

native_browser_contract.rs:5 新增:

const ENTRY_SHA: &str = "992174a5e637645aeb444adfdb1bae688e997bb84d7db07532f68e358e60f278";

配合 :201-205,对入口 cua-repl.mjs 做无条件的硬哈希准入:

let entry = "bin/node_modules/@oai/cua-repl/bin/cua-repl.mjs";
ensure!(
    sha(&read_regular(&runtime.join(entry), 1024 * 1024)?) == ENTRY_SHA,
    "Unsupported native worker entry protocol"
);

main 里没有 cua-repl.mjs 的哈希常量(grep -nE '^const [A-Z_]+_SHA' 只有 ORIGINAL_SHA / NATIVE_SHA / CURRENT_ORIGINAL_SHA / CURRENT_MANIFEST_SHA,且 CUA_ENTRY 只作存在性检查)。

后果:下一个 CUA 版本只要动一次 cua-repl.mjs,detect 一律失败、整条链路 fail-closed。 这正是 04ab6290 明确修掉的模式。

你在描述里说「不是重复追加 0.0.27 哈希」——字面属实(0.0.27 的入口文件确实没变,所以常量没升级)。但这条哈希的存在本身就复现了 issue #2294 的根因,属方向回退。

1.3 PROTOCOL_SHAPES 是换个位置的哈希白名单

// native_browser_contract.rs:6-9
const PROTOCOL_SHAPES: [&str; 2] = [
    "5bf64da3b8386af46a6fb2c2d8829a507130ba9896237a813b8e04c4ce8eb515",
    "eae1b49427aebf3ed3d1119de1c303125c4f78b3b0ca644f055ed07ec2c2be30",
];

sendSessionRequest 方法体的规范化 AST 哈希必须落在这两个值之一(:32-38 的 Binding::replace 里是硬 ensure!)。

我实测了两条路径的行为差异:

输入 main 路径 本 PR 路径
重命名 sendSessionRequest 参数及全部引用 接受 接受(AST 归一化确实生效)
方法体内加一句 this.metrics=(this.metrics||0)+1; 接受 拒绝
"getUserTabs" → "getUserTabs2" 接受 拒绝
方法改名 sendSessionRequest → sendSessionRequest2 接受 拒绝

第一行说明你的 AST 校验确实做到了 main 做不到的绑定级校验——这是真增量,值得保留。后三行同时说明:「未知版本能不能用」被重新绑死在一张双元素哈希表上。 issue #2294 的病根(一次发版作废整张表)从原件层搬到了 AST 层。

1.4 版本下限检查被静默弱化

main 在未知路径要求 parsed.at_least(MIN_RUNTIME_VERSION)(0.0.11)。本 PR 的 detect 完全不解析 manifest 版本,只比对 layout 字段。虽然 ENTRY_SHA 实际形成了隐式下限,但性质不同(显式契约 vs 巧合),应显式补回。

二、确属增量的部分(应当保留,不要一起砍)

这三点 main 确实没有,不是重复:

  1. 组件级事务守卫升级:detect 对 node.exe / node_repl.exe 做 PE x64 头校验(pe_x64)、对 package.json 校验 name / type / exports["./service"],并把现算哈希写进 contract.files 作为精确事务守卫。比 main 的「仅存在性+大小」强一个量级。
  2. schema-2 可重算恢复:recovery_material 对 schema 2 要求 binding.replace(&original, control) == candidate——恢复时重算最小变换并要求逐字节相等,而非只比哈希。main 只有 schema 1。这是真正的抗篡改改进。
  3. 绑定级 AST 校验(见 1.3 第一行)。

净判断:增量存在,但被一个方向性回退抵消。 正确的合入形态是「保留这三点,去掉 1.2 / 1.3 的硬编码准入面」。

三、新增依赖:合规,但有两点要补

@babel/parser 与 @babel/traverse 我核对过:依赖树零膨胀——main 的 lock 里本来就有这两个(经 @vitejs/plugin-react → @babel/core 传递引入),版本恰好就是 7.29.3 / 7.29.0。本 PR 只是把传递依赖提升为直接 devDependency(188 → 188 packages,added/removed 均为空)。

写成精确版本而非 ^ 是正确的:inspect-service.mjs 里那些 AST 规范化哈希是对 Babel 输出形状的承诺,Babel 变更节点结构时哈希会静默漂移。建议补一条注释说明这个耦合。

要补的两点:

  1. assemble-native-browser-inspector.mjs 没接进任何 CI workflow。仓库对 renderer-inject 有 assemble --check 约定,这里缺了。native-browser-contract.test.ts 只校验 .cjs 头部含 .mjs 的 SHA256(弱防线,不验 bundle 内容)。建议把 --check 加进 .github/workflows/pr-build.yml。
  2. esbuild 是未声明的构建依赖。脚本从 node_modules require esbuild,而它只作为 vite 的传递依赖存在。vite 升版换掉 esbuild、或 npm 不做 hoist 时,构建脚本会静默失效。建议显式声明。

另外 package-lock.json 里根 version 从 1.3.0 改成 1.5.0——那是 main 自身的 lock 漂移被顺手补上,不是本 PR 引入的。建议拆成独立 commit,可减小 rebase 冲突面。

四、实跑验证

我在独立 worktree(/tmp/pr2378wt)实测:

项目 你声称 实测
cargo test -p codex-plus-core 1708 passed / 0 failed / 8 ignored 1383 passed / 0 failed / 6 ignored
npm test 316 passed 315 pass / 0 fail / 1 skip
npx tsc --noEmit 通过 通过
assemble-native-browser-inspector.mjs --check 通过 通过

1708 vs 1383 是口径差异(你应是 workspace 口径,我只跑 -p codex-plus-core),不是虚报;你「ignored 不计为通过」的表述是诚实的。

但有一个实质问题:被 skip 的那个测试 real local service fixtures retain their structural contract 需要 CPP_BROWSER_SERVICE_FIXTURES 环境变量,CI 不设。而你描述里最有价值的负例(真实 0.0.11/0.0.24/0.0.27 服务识别、歧义、遮蔽、契约漂移)全部集中在这个被 skip 的测试里——也就是说「真实版本负例通过」在 CI 上不可复现。

同样,Rust 侧 structural_fixture_transaction_recovery_and_component_drift 需要私有 fixture,CI 上永远 skip。结果:structural::detect 这条全新代码路径在 CI 上零覆盖。 建议用合成 runtime(tempfile + 假 PE + 假 package.json + specimen 服务)在 #[cfg(test)] 里直接调 detect。

五、安全审查:声称基本属实

逐项核对 native_browser_contract.rs:94-116:CREATE_NO_WINDOW、.env_clear()(仅回填 SystemRoot,这是必要的)、--no-addons、--max-old-space-size=256、20 秒截止(try_wait 轮询 + ensure!)、stdin writer 独立线程——全部落实。

写盘路径质量很高:write_new 用 create_new(true) + sync_all();已存在则逐字节比对、外部改动直接拒绝;pin_parents 在 Windows 上用禁 DELETE 的 share mode + OPEN_REPARSE_POINT 防 TOCTOU 重解析点替换;schema-2 恢复不执行 Node(纯计算)。

worker 身份认证 / 伪造官方登录:独立确认没有触碰。 我 grep 了新增面里的 apikey / auth / login / token / credential,命中全落在 Babel 的 MIT 许可证文本里;require-identification.mjs 与 main 逐字节相同。你的声明属实。

两点建议写进文档:

  1. detect 对 node.exe 只验 PE 头、不验哈希。所以「AST 校验能防住恶意 runtime」要打折:攻击者若能同时控制 cua-repl.mjs(受 ENTRY_SHA 保护)则防得住,若只能替换 node.exe 则防不住——因为 helper 是用 runtime.join("bin/node.exe") 解释执行的。这是本方案最深的信任根,建议显式写出。
  2. run_child 超时路径上的 writer.join() 隐含依赖 EPIPE 返回(实践中成立,但属隐含保证),建议改为先 kill 再关 stdin,或用有界等待。

六、必须修改的点

  1. 删除 ENTRY_SHA 硬哈希准入(:5、:201-206),改为结构不变量:入口文件存在 + parse 成功 + 导出签名形状匹配,与 main 的 CUA_ENTRY 结构检查对齐。现算哈希保留作事务守卫(这个用法是对的),但不得作为准入条件。
  2. PROTOCOL_SHAPES 降级为「已知良好快路径」而非唯一准入,否则与 main 的 adaptive 降级语义直接冲突。
  3. 修复 Structural 通道被旁路(native_browser.rs:684-687):让 detect 成为 for_manifest 未知分支的加固而不是替代。
  4. 补回运行时版本下限检查,detect 里显式解析 manifest 版本。
  5. assemble-native-browser-inspector.mjs --check 接入 CI。
  6. 显式声明 esbuild 到 devDependencies。
  7. 给 detect 补 CI 可跑的测试(合成 runtime,不依赖私有 fixture)。
  8. 拆分 package-lock.json 的版本号修正为独立 commit。
  9. 文档补三处边界:node.exe 只验 PE 头是信任根;文档里「不依赖外部 parser 包」的表述需与实际 devDependency 一致;schema-2 的向前兼容风险。

七、与 #2309 / #2313 的合并顺序

你们三个 PR 都动 native_browser.rs 且都不含 04ab6290:

#2309: merge-base 693c8486,不含 04ab6290
#2313: 同上
#2378: merge-base 09a9f1c,不含 04ab6290

相对 main,#2309 / #2313 的 diff 显示 native_browser.rs -770 / -767 行、native_browser_connection.rs -387 行——它们在整体回退 main 的 native-browser 工作(基线落后所致,非本意)。

谁先合都会互相覆盖,必须先 rebase。 建议顺序:

  1. 先合 fix(browser): Windows API 浏览器结构兼容加固与可验证恢复 #2378 的 rebase 版(三者中唯一主动承认需协调、且技术增量最实),但先按第六节去掉硬编码准入面;
  2. #2309 / #2313 在此之后 rebase,重新评估 native-browser 部分——大概率不该保留任何 native_browser.rs 改动,只留各自主功能;
  3. 若 fix: 新版 Codex App 兼容适配(面板边界、顶部入口与注入卡顿缓解) #2309 / fix: make launch and restart lifecycle deterministic #2313 先合,04ab6290 与本 PR 的成果都会被静默回退。

另有一处静默丢失风险:apps/codex-plus-manager/src/native-browser-status.ts 本 PR 未改,但 main 刚在 a6e0d7a8 加了 browserRecognizedSuffix(区分「扩展未连上」vs「连上了但版本未登记」)。它不在本 PR 的改动文件列表里,所以 rebase 冲突不会提示——需要人工确认别丢掉。

BigPizzaV3 added a commit that referenced this pull request Oct 2, 2026
当晚新提的 PR(@Yuimi-chaya,+1370/-16,11 文件),与已合入的 04ab629 改同一块
native_browser.rs。处置:要求修改。

三条经独立核实的阻断项:
1. FileCheck::Structural 降级通道被整体旁路——for_manifest 改收哈希字符串,
   未知版本必然 Err,main 的结构降级分支在未知版本上不可达。
2. ENTRY_SHA 重新引入哈希白名单(无条件准入 cua-repl.mjs 入口),
   复现 issue #2294 的病根;PROTOCOL_SHAPES 同理。
3. 运行时版本下限检查被静默弱化。

确属增量应保留:组件事务守卫、schema-2 可重算恢复、绑定级 AST 校验。
依赖合规(@babel 零膨胀,188→188),但构建 --check 未接 CI、esbuild 未显式声明。
实测 1383 passed / 315 pass,但最有价值的负例全在被 skip 的 fixture 测试里,
detect 路径在 CI 上零覆盖。

安全:子进程隔离与写盘原子性落实,未触碰身份认证(已独立确认)。
合并顺序:与 #2309/#2313 三者都不含 04ab629,必须先 rebase。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Yuimi-chaya
Yuimi-chaya force-pushed the codex/browser-structural-compat-20261002 branch from 932abca to d49bd54 Compare October 2, 2026 14:47
@Yuimi-chaya Yuimi-chaya changed the title fix(browser): Windows API 浏览器语义契约兼容与可验证恢复(异机实测通过) fix(browser): Windows API 浏览器结构兼容加固与可验证恢复 Oct 2, 2026
@Yuimi-chaya

Copy link
Copy Markdown
Contributor Author

已按补审意见整合到 main 的 89615978,当前 HEAD 为 d49bd549,冲突已在本地解决:

  1. 保留上游 for_manifest / FileCheck::Structural / adaptive 通道和最低版本要求;不是捕获任意 manifest 错误后替换为另一条准入路径。未知版本经过结构检查,再获得 AST 绑定与组件快照。
  2. 删除未知版本的 ENTRY_SHA 准入;入口改验模块绑定和 awaited launch() 签名。PROTOCOL_SHAPES 不再限制恢复记录,检查器中的已知方法摘要只是快路径,新增绑定与请求头数据流校验。计数插桩、内部方法改名和无关命令排除项变化均有公开正例;请求头覆盖、重绑定、握手和回调漂移有公开负例。
  3. 补回版本下限时发现真实 0.0.27 manifest 没有 name/version,而是 runtime_archive_version/runtime_archive_name。已兼容实际生成格式,校验二者一致性及最低版本;不是因缺字段放弃版本检查。真实原件临时副本已通过完整事务测试。
  4. 保留 PE / package 导出检查、组件事务守卫、schema 2 可重算恢复、schema 1 和连接识别 UI。native_browser_connection.rs 与 native-browser-status.ts 相对当前 main 没有差异,未丢失 browserRecognizedSuffix。
  5. 三个 CI 构建均增加 bundle --check,显式声明现有 esbuild 精确版本,说明 Babel / esbuild 的可复现性耦合;lockfile 根版本修正独立为第二个提交。
  6. 新增公开 specimen 和合成 runtime 测试,CI 直接跑检测、格式 / 版本拒绝、组件漂移及无 Node 恢复,不再依赖私有真实服务才能覆盖新路径。文档补齐 Node 信任根、构建依赖和 schema-2 向前恢复约束。
  7. 检查器改用常规文件 stdin,不再有超时后 writer.join() 等待管道结束的问题;保留隐藏启动、清理环境、20 秒截止和 kill/reap。

最新本地结果:Rust workspace 1724 passed / 0 failed / 8 ignored;真实 0.0.27 临时事务 1 passed;真实三个版本原始服务定向 6 passed / 0 skipped;默认前端 336 passed / 0 failed / 1 skipped,该私有夹具跳过项已另行定向运行。TypeScript、Vite、两种组装检查和 diff 检查通过。原生服务没有执行,没有关闭、重启、替换或安装真实应用。

正文区分了旧 932abca3 的异机验收和当前整合版本的自动化验证,未把旧安装包或真人结果冒充为新 HEAD 的验收。

另外,第七节引用的 #2309 / #2313 旧基线已经过时:两者已分别更新到 d5eff418 / 540b8628,都包含 04ab629,本轮核对均可合并且 Windows / 两个 macOS CI 全绿。两条 PR 仍独立;不会用取旧文件整份覆盖 main 的方式合入。

@Yuimi-chaya

Copy link
Copy Markdown
Contributor Author

首次新 CI 的三处失败已定位,均为 Check native browser inspector bundle 的 Inspector bundle drift,不是原生浏览器或 Rust 测试失败。Windows 的前端测试也已先行通过。

原 workflow 使用 npm install --package-lock=false,会忽略 lockfile 中传递依赖的精确版本。虽然 Babel parser / traverse 和 esbuild 已固定直接版本,完整 bundle 仍包含其传递依赖与许可证;安装树不锁定就不能要求生成字节相同。

已追加 cea82591:两组 frontend 安装步骤改为 npm ci,三平台继续保留完整 bundle --check,没有放宽检查,也没有修改浏览器实现。

在全新的隔离目录运行 npm ci --offline 后,bundle 完整检查、前端 336 passed / 0 failed / 1 private-fixture skipped、TypeScript 和 Vite 构建全部通过。现有共享依赖未替换,Rust / 浏览器源码未改,先前 1724 项 Rust 及真实临时副本验证仍适用。新提交会重新触发三平台 CI,尚不据此宣称远端全部通过。

@BigPizzaV3
BigPizzaV3 merged commit 3ef8998 into BigPizzaV3:main Oct 2, 2026
3 checks passed
@BigPizzaV3

Copy link
Copy Markdown
Owner

已合并到 main(4484668d)。感谢这个 PR —— 上一轮我提的 5 个阻断项你全部改到位了,复验时逐条确认过:

  • for_manifest 收回收字节切片,未知版本经 parse_manifest + MIN_RUNTIME_VERSION + 组件检查后走 Self::adapted,FileCheck::Structural 降级通道真的通了;
  • ENTRY_SHA 常量全仓零命中,入口改为结构不变量(Babel 解析 cua-repl.mjs,要求恰好一个 namespace import + 恰好一次 await ns.launch() 且无参);
  • PROTOCOL_SHAPES 降级为快路径(if (!protocolShapes.has(contractSha)) adaptiveRequest(send););
  • manifest 版本下限(含新增的 archive 形式校验);
  • CI 公开 specimen 让新路径真的被覆盖(实测不是 skip)。

合入时我做的两处处理

一、与 #2313 的冲突(已解)。 两个 PR 都在 native_browser.rs 加了测试函数,git 把公共结尾提取成了公共后缀。我按「两边各自的函数体 + 各配一份后缀」重建,保留双方测试。

二、一处 git 不会报、编译才发现的语义冲突。 main 上的测试 control_path_substrings_are_not_identifier_drift 手工构造 RuntimeContract,而本 PR 新增了 binding 字段,合并后该构造缺字段、无法编译。我补了 binding: None 并加注释说明该用例不经结构探测。

这正是我每次合并后都要实跑测试的原因——文本冲突解决了不代表语义冲突解决了。

当前状态

cargo test -p codex-plus-core --lib → 520 passed / 0 failed。

两点建议后续跟进(不阻塞,已合入)

  1. 可移植性:plain_path 会对路径的所有祖先做符号链接检查并 fail-closed,而 macOS 的 /var 本身就是符号链接,所以本地 cargo test 会挂 2 例(把 TMPDIR 指到无符号链接目录即通过)。CI 不受影响(只有 Windows 跑测试),但对 macOS 开发者是个坑。测试里对 tempdir 做一次 canonicalize() 即可。
  2. 同构残留:inspect-service.mjs 里 metadata / policy 两个回调函数体的 AST 哈希仍是硬绑定的两个值。fail-closed 所以安全,但建议写明「这是有意保留的语义锚点」,否则下一个小改动可能就得再追一个哈希。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants