Conversation
force-off 的判据 ALPHA_CLOUD_MCP_URL 由 applyAuthEnv() 写入,而它经 initAuthEnv() 在 whenReady 之后才跑,晚于 preferAppEnv()。冷启动的登录用户在 preferAppEnv 里恒判「登出」, force-off 恒不触发,反把 OPENCODE_ENABLE_EXA 设成 "1" —— fork 出去的 sidecar 里本地 keyless 与 cloud_web_search 双活,ADR-009 B1「云优先、只剩一个 web search 工具」被破。 修法不动 preferAppEnv 在 index.ts 的位置(它必须早于 fork,要供 PATH 等),而是把闸抽成 导出的 applyWebSearchSovereignty(),在 spawnLocalServer() 里 syncSecretFiles 之后、fork 之前**每次**重算 —— 与 A6 密钥文件同纪律,两个判据(URL + 密钥文件)此刻才双双成立。 闸做成幂等的双向函数:登录/kill-switch 覆盖写 "0";登出/BYOK 还原 keyless 基线 + 桌面默认。 只单向 force-off 会让一次登录把 keyless 哑到重启为止 —— 该洞今天不显形,只因闸从未真正触发。 测试改走真实顺序(preferAppEnv → 写 auth env → 断言 fork 时的 env),不再手工预置判据; 去掉 force-off 调用后 3 条断言转红。 Fixes #621
打包端真正挂载的 web search 是 packages/opencode/src/tool/websearch.ts。它此前有两处失守: `output: result ?? "No search results found…"` 把空/坏响应伪装成成功串(模型无从分辨「零命中」 与「响应坏了」),`.pipe(Effect.orDie)` 把一切错误塌成匿名 defect(无类别、无状态、工具崩溃)。 改为单一可辨类型 WebSearchFailure,失败集与 alpha-platform `POST /v1/tools/web_search` 契约 对齐:401 / 403(带 error.code,action_forbidden 与 job_not_enforceable 可区分)/ 400 / 402(accountPreauth 拒绝、per-job 超预算)/ 502 / 其余非 2xx 一律 unexpected_status;另加 timeout / transport / provider_error(HTTP 200 但 MCP isError)/ empty_result / invalid_response。 不再用 filterStatusOk —— 它把每个非 2xx 压成同一个 StatusError,状态与 body 都拿不回来。 传输层 defect 经 catchCause 收进同一类型,不再是未处理 defect。 websearch.ts 侧把它转成 canonical 的 ToolFailure 再 die:legacy 工具链跨 Promise 边界后, AI SDK 与 native adapter 两条路都能把它结算成模型可见的 tool error,消息带类别+状态+上游 body。 云失败不静默切回 keyless,空结果不再当成功。 刻意不动 mcp/catalog.ts 与 tool/code-mode.ts:云路径在 isError 时已经带 body 抛错(已 loud), 为了加一层分类前缀去扩大上游收编面不划算。 Fixes #489
#489 此前卡在 north-star 守卫:packages/opencode 在 UPSTREAM_PATHS 内,守卫用 --diff-filter=DMR,改既有文件必红。owner 裁决走 ADR-029 L3 文件级 exclude(ADR-033 先例)。 ADR-035 按 ADR-029 §3 逐要件登记:被接管表面(websearch.ts / mcp-websearch.ts + 随源测试)、 L0–L2 为何不够用(tool.execute.after 只在成功路径触发;失败语义是控制流不是文本变换; alpha 至今没有 L2 patch 施加机制,为两个低 churn 叶子新建一台机器不划算)、守卫形态、 回退方案、放弃白嫖范围声明(单向门,含上游安全修复)。云路径的 catalog.ts / code-mode.ts 已 loud,明确不收编 —— 自限写进 ADR,后续要收须自己的 ADR。 顺带修一处既有漂移:scripts/alpha-check.sh **完全没有** exclude 清单、UPSTREAM_PATHS 也停在 ADR-033 之前的五个包,即本地守卫自 ADR-033 起对 16 个被接管/生成文件恒报假红,与 CI 早已不是 1:1。恒红的门等于没有门,故与 alpha-ci.yml 逐条对齐。 Fixes #489
ADR-009 §裁决(d) 与 E7 基线 §1/§3b/票2/票3c/票5/票6 里「host-tool 无 402/余额面」「web_search 仍 settle-only 未入册」「403 = scope_forbidden」三处表述今天都是错的,照办会漏掉真实失败态。 平台侧实况(alpha-platform packages/gateway/src/worker.ts @ 2fd1984):/v1/tools/web_search 已入 BILLABLE_ROUTES(:92)且由启动断言 assertRegisteredBillableRoutes(app.routes)(:952) 与真实 router 双向核对(ADR-018 对 web_search 已满足);handler(:849-871)有 accountPreauth (:867-871 → 402)与 per-job 预算 precall(:863 → 402);403 的码是 action_forbidden(:853-854) ——scope_forbidden 属另一个 worker(server.ts:59),不在这条链上。 同时登记:#621 冷启动时序缺陷(force-off 判据晚于 preferAppEnv,云优先在冷启动路径上从未成立) 与 ADR-035 的两文件 L3 收编;把 ADR-009 的「未竟 · #489 暂缓」条目换成已交付。
Codex 对抗审计判 NOT-MERGE(1 Blocker + 5 Major + 1 Minor,多条动态复现)。逐条闭合:
Blocker(umbrella 绕过):`OPENCODE_EXPERIMENTAL=1` 让上游 `enableExa` 恒真,主权闸把四个
keyless flag 写 "0" 也关不掉本地 `websearch` —— 代付态本地+云双活,kill-switch 下云暗而本地
仍活。走 ADR-009 裁决 (b) 路 (i):注入面对本地工具 ID 也写 permission deny,并压平 alpha 自己
注入的三个 agent 的 `websearch: "allow"`(引擎 agent 级规则并在全局之后,不压平则全局 deny
对它们无效)。零改 umbrella、零改上游 registry。
Major 2(平台 402 到不了 payment_required):事实是平台 cloud MCP 薄壳 `text(body, !r.ok)`
丢弃 HTTP 状态,且 gateway 的两条 402 body 不带 `error.code` —— 不改平台就无法在客户端分类。
不改 alpha-platform 仓,改为:①ADR-035「HTTP 状态在 body 透传」的错误表述作废,ADR-009 与
E7 基线同步标明「可分类范围 = 本地 Exa/Parallel 直连链路」;②新增走**真实 McpCatalog.convertTool
链路**的测试把「loud + 原 body 完整,但无状态、无分类」钉成回归基线(含反向断言);
③开 alpha-platform#105 要求透传状态 + 补 402 error.code。上一轮把平台响应直接喂本地 call()
的测试是假接线证明,已删除。
Major 3(baseline 取点):基线改在登录 shell env 合入之后截取。此前 Finder 首启 + 用户
`export OPENCODE_ENABLE_PARALLEL=1` 时基线为空,登出「还原」反而删掉用户真值并默认开 Exa。
测试换成真实登录 shell 探测(SHELL 指向真脚本、spawnSync 真跑),不再用 SHELL=nu 跳过导入。
Major 4(secret 同步失败仍 fork):改 fail closed —— 同步失败拒绝本次 fork,不再 catch 后继续
(旧 token 文件会让主权闸误判 platformPays,新 sidecar 带作废 token 注册云工具)。
Major 5(零命中/错误判定):`structuredContent` 进 schema —— 「content:[] + results:[]」是
合法零命中成功;200 且负载为结构化 `{error:…}`(未置 isError)判 provider_error 并带 code;
200 的 HTML/非 JSON 错误页在 empty_result 里附上原 body。
Major 6(传输有界):headers + body 读取放进同一个 timeout,body 边读边计数、2MiB 处停手。
Minor 7(整文件 exclude):上游 `test/tool/websearch.test.ts` 还原受守(整文件 exclude 会连带
放行其中 `registry.ts` webSearchEnabled 的断言),新增断言落 alpha 自有的
`test/tool/alpha-websearch-failure.test.ts`(新增文件不触发 --diff-filter=DMR,无需 exclude)。
两处 exclude 表同步减一条并保持逐条一致,ADR-035 登记。
R2 判 NOT-MERGE 的三条未闭合项。 Blocker 1(主权 deny 可被覆盖):R2 动态探针实测 configOnly=deny,但加入 session allow 或 approved 后均变 allow。三条绕过——① 别的 config 源后加载的 agent 写 "*": "allow"(agent.ts:293 并在全局之后 + evaluate 取 findLast); ② PromptInput.tools.websearch=true 被 setPermission 持久化进 session (prompt.ts:1060),tools.ts:87 再并在 agent 之后,respawn 也带得回来; ③ approved 排在整个 ruleset 之后(permission/index.ts:73)。 结论:permission 注入不是主权保证。最终规则改放在**工具自身** —— applyWebSearchSovereignty 每次 fork 前把判决写进 ALPHA_LOCAL_WEBSEARCH_DENY (sidecar-env 白名单放行),ADR-035 已接管的 tool/websearch.ts 在 execute 首行读它并以 ToolFailure 拒绝;它不查 ruleset,故没有任何 permission 规则能 覆盖。这是最窄的解:零新增上游接管(该文件已在 exclude 清单内),不动 permission/index.ts 的求值序,也不动 registry.ts 的注册闸。注入面的 deny 保留 但降级为可用性(把工具从模型工具表滤掉),并顺手修好 agent wildcard 压平 (以前只改已有的精确键,且不钉末位)。三条绕过各有一条反向测试,走真实 Tool.init → execute 路径:先断言该绕过在 permission 层真的判 allow,再断言 工具仍拒绝且传输层零命中。 Major 6(读取不硬限):实现先整块 push 再判越界,单个 3 MiB chunk 实收 3,145,728 字节而声明上限 2,097,152。最后一块改为只保留剩余可读字节, 补一条按字节精确断言的测试。 Major 5(结构化 error 的 code 到不了模型面):message 以前只在有 HTTP status 时才拼 code,而工具边界只把 message 交给模型。改为 status/code 各自独立出现, 断言从最终 ToolFailure 面取。 文档:ADR-009 / ADR-035 里「主权已穷尽」「2 MiB 处停手」「provider error 带 code」三处与事实不符的已完成声明全部更正;并登记云链路反向断言的到期条件 (硬编码现状 fixture,alpha-platform#105 落地后不会自动变红,届时须改成 status/code 的正向断言)。
Blocker 1(V2 Core 副本旁路):R3 勘破打包 sidecar 的 HttpApi 同时挂载 V2 Session 路由与 Location 服务,core 的 BuiltInTools 里是第二份已挂载的同名 websearch 注册 ——它不读 ALPHA_LOCAL_WEBSEARCH_DENY,走自己的 PermissionV2 后直接调 Exa/Parallel。 R2 的「最终闸」只覆盖了 legacy 一份。 按类修而非按实例修: - packages/core/src/tool/websearch.ts 的 execute 首行加同一条闸(ADR-035 §1 按同一 裁决追加接管这一个同类叶子,两处 exclude 表逐条同步)。更窄的解逐条勘探否掉: V2 结算不触发任何 plugin hook;BuiltInTools 是静态 deps,删掉等于永久去掉工具而 判决是每次 fork 动态重算;V2 permission 与 V1 同病。 - 新增普查闸 packages/ui-mac/src/main/websearch-copies.test.ts:钉住「全仓注册为 websearch 的源文件集合」与「全仓直接引用 Exa/Parallel 端点的源文件集合」,并要求 每一份注册都读同一个主权信号、闸排在任何 permission 交互/出网调用之前。第四份 副本出现即红(已用模拟副本验证)。 - 真实 V2 链路回归 packages/core/test/alpha-websearch-sovereignty.test.ts:真 ToolRegistry.materialize() + settle();含「显式 allow 的 ruleset 顶不掉」与 「permission 层根本没被咨询」。 云工具 kill-switch(R3 判门控):先验证过的更窄方案被否——ConfigMCPV1.Remote 只有 整 server 的 enabled,没有 per-tool 过滤,「注册期就不提供这个工具」在引擎侧不存在; 整 server 关会误伤兄弟工具。最终闸因此落在 alpha 自有的 ext 插件 (packages/ext/src/cloud-websearch-kill.ts),由 tool.execute.before 钩子首行调用: 普通 MCP(session/tools.ts)与 code-mode(tool/code-mode.ts)两条链都在 ctx.ask 之前触发它,而 Plugin.trigger 用 Effect.promise 调 hook 且不捕获,抛出即终止调用。 钩子不查任何 permission ruleset,故后置 agent/session allow 与 approved 都够不着。 判决经 ALPHA_CLOUD_WEBSEARCH_DENY 过河(main 每次 fork 前两个方向都写,白名单放行), 只在 kill-switch 时置位——代付态云工具是权威通道。零上游接管。 fail-closed:kill-switch 且 ext 未装载时索性不注册整个 cloud server 并 loud,宁可 连兄弟云工具一起损失,也不放一个活的 web_search 出去。 文档:ADR-035 §背景 2 的「core 的 v2 builtin websearch 永不挂载」作废更正(它是 Blocker 1 的源头);ADR-009 裁决 (a) 里「permission deny 即 kill-switch 保证」、 裁决 (b) 二次收口的「能力真关」、后果里「跨本地+云关掉」三处与运行时事实冲突的 声明全部按本轮真实结论改写,并诚实登记未闭合残留(远端 catalog 仍 advertise; 闸够不着绕过引擎工具循环直发 MCP 的客户端;平台侧仍会接受并计费直发调用)。 同时登记:engine 侧两个回归文件跑在 alpha 合并闸之外,机制事实已固化在 ui-mac/ext 的测试里。 验证:north-star 守卫绿(exclude 拿掉即红);typecheck 错误集与 base 逐字相同 (196 条,全是冻结前端 packages/app 的既有假红);ui-mac 2883→2892 pass、ext 90→99 pass、contracts-consumer 19 pass,0 fail;core alpha 回归 9 pass、 opencode alpha 失败面 38 pass;docs gate 绿;git diff --check 干净。 六条单点回退变异全部验证变红后还原。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
R4 判 NOT-MERGE 的两条,逐条闭合。
## Blocker 1:源码普查网可绕,闸落到共同执行边界
R4 的可执行反例:`const id = ["web","search"].join("")` + `Tool.define(id, …)`
复用既有 `McpWebSearch.call` —— 注册名算出来(Net A 看不见)、传输已白名单
(Net B 看不见新 URL)、新叶子不读主权信号。实测 netA=false, netB=false。
「两个已知实例加闸 + 源码盘点」是实例修补,不是类级规则。
闸因此下沉到**共同的执行边界**:本地 keyless web search 的两条出网出口,
两条都已在 ADR-035 接管面内,现在都在第一句读 ALPHA_LOCAL_WEBSEARCH_DENY:
- packages/opencode/src/tool/mcp-websearch.ts 的 `call()`(legacy 唯一传输)
—— 新增 sovereignty_denied 失败类别,其 message 即「别重试」原文;
主权信号的**声明点**下沉到此,叶子改为转出,免两处漂移。
- packages/core/src/tool/websearch.ts 的 `callMcp()`(V2 Core 那份的传输)
—— 导出,使「复用传输」成为新副本的正确写法;拒绝时直接 ToolFailure,
因为 ToolRegistry.settle 只把 LLM.ToolFailure 结算成模型可见 error,
于是连一句错误映射都没写的副本也拿得到正确拒绝。
叶子 execute 首行那道闸保留为纵深;两张源码普查网保留但降为纵深(抓自带全新
HTTP 出口的副本),不再声称穷尽。
## 云 kill-switch:ext 缺席判断绑到「确认装载 + 钩子已注册」
alpha-config-injection.ts 原来只看 extPluginPath 存不存在 —— 路径存在不等于
插件装载。三条路径仍在而钩子不存在的真实链路:OPENCODE_PURE=true(经
sidecar-env 前缀规则进 sidecar → runtime-flags 判 pure → plugin/index 整个
跳过外部插件)、bundle import 失败、AlphaExt 初始化失败(后两者 log-and-continue)。
改为握手:kill-switch 下注入面只写一个 enabled:false 的 cloud server 并置位
ALPHA_CLOUD_MCP_ARM;打开它的是 ext 自己 config 钩子里的 armCloudMcp()。
该钩子能执行 ⇔ 插件已返回 hooks ⇔ 同一对象上的 tool.execute.before 闸已注册。
arm 通道刻意不进 sidecar-env 白名单,外部 shell 伪造进不来。
## 变异验证(实跑)
- R4 那段构造作为变异种真跑:opencode 与 core 各一组,断言传输层拒 + 零出网 +
模型面拿到主权拒绝原文,各带一条闸不置位时确实出网的正向对照。
- ext 缺席三态各跑一次(真 AlphaExt 动态 import + 真失败模块),断言云 server
停在 enabled:false;OPENCODE_PURE 的解析由真 RuntimeFlags 断言。
ADR-009 / ADR-035 中被本轮推翻的表述(「ext 未装载即 fail-closed」按路径判、
「普查网 = 类闸/按类闭合」)已改成与实现事实一致,并登记不再声称穷尽。
零新增上游接管:三个源文件都已在 ADR-035 的 exclude 清单内。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocker(通用 Remote MCP 是第三条现存出口):配置里直接声明任意 remote MCP
(`{"mcp":{"exa":{"type":"remote","url":"https://mcp.exa.ai/mcp"}}}`)会产生
`exa_web_search_exa`,普通模式与 code-mode 都直接 `client.callTool()`,R4 那两道
传输闸一概不经过。收口 = 把本地判决接到两条链**共同的钳制点**:ext 的
`tool.execute.before`。`ALPHA_LOCAL_WEBSEARCH_DENY` 置位时,任何 MCP server 上的
web-search 形态一律拒;唯一例外是 alpha 自己注册的云 server(名字经新的
`ALPHA_CLOUD_MCP_SERVER` 过河),代付态它是权威通道、只有 kill-switch 关它。
传输层两道闸保留为本地 `websearch` 工具的纵深。
> 产品语义(已在 ADR-009 登记并标「归 owner 复核」):这等于主权态下连用户自己
> 配置的 web-search MCP 一起关。按「关」实现。
Major(R4 增量回归:`enabled:false` 不是不可重开):`MCP.connect()` 无条件把配置
复制成 `enabled:true`,该能力公开为 `/mcp/:name/connect` 且产品 UI 真的在调 ——
ext 缺席时用户点一下就能把那份含完整 URL/header 的 server 热连起来。改成
kill-switch 下**完整定义根本不进配置**:注入面只经 `ALPHA_CLOUD_MCP_ARM` +
`ALPHA_CLOUD_MCP_DEF` 托管,由 ext 的 `installCloudMcp()` 在 config 钩子里装进去
(`{file:}` 引用由它自己解析,读不到密钥即 fail-closed 不装);继承来的同名条目
一并抹掉。ext 缺席 ⇒ `/mcp/cloud/connect` 直接 NotFound,无物可复活。
Minor(`ALPHA_ENV_ALLOWLIST_EXTRA` 可放行 ARM):R4 的「shell 进不来」不成立 ——
三个握手变量都不是 credential-shaped,逃生阀点名即放行。文档与测试改成真判据:
可信度来自「injectAlphaConfig 每次 fork 的每条分支都覆盖或删除它们」+「ARM 与 DEF
必须成对」,两条测试各钉一半。
变异验证(真实配置,非替身):
- `packages/opencode/test/tool/alpha-mcp-websearch-gate.test.ts` —— 真 streamable-HTTP
MCP server + 真 `mcp` 配置段 + 真装载 `packages/ext/src/plugin.ts` 本体;普通模式与
code-mode(真 `CodeModeTool` → `invokeChildTool`)各跑一次,断言被拒 + 远端零
`tools/call`,各带一条闸不置位时真的出网的正向对照。
- `packages/opencode/test/mcp/alpha-cloud-mcp-revival.test.ts` —— 真 MCP lifecycle +
真 HttpApi 路由;正向对照先证明 R4 形态确实能被 `/connect` 热连,再断言 R5 形态
404 + 远端零请求;`add` 只带 enabled:true 走不通(400)。
诚实登记的残留:`POST /mcp` 的 `add` 自带完整定义能新装第三方 MCP(拦它等于拦任意
第三方 MCP,需收编 handlers/mcp.ts / mcp/index.ts,不在本票);ext 缺席时第三方
web-search MCP 无拦截点;判据认 `web[_]search` 词根,不谎称穷尽。
零上游改动(两个 opencode 测试是新增文件)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
R6 判 NOT-MERGE 两条,本轮各自闭合并同步收窄 ADR 宣称。 Blocker —— 按名字分类既漏又可伪造: - 分类扩宽但**降级为尽力而为**。判据从 `web_?search` 单词根扩为「去分隔符后含 websearch/searchweb」∪「search 与 web/internet/online 或一个具名搜索引擎词相邻」, 收进 R6 点名的 search_web / web-search / websearchTool / brave-search;非 ASCII 名 经 McpCatalog.sanitize 后只剩下划线,**按名字识别不可能穷尽**,该漏以回归登记。 - 治理豁免改绑**端点身份**。ALPHA_CLOUD_MCP_DEF 里 alpha 本次 fork 自己写的定义 url 是不可伪造真源;ext 的 config 钩子用 recordMcpOwnership() 拿引擎已合并完成的配置 逐个核对(名字 = 点名的那个 ∧ type=remote ∧ url 逐字相同),工具归属的任何歧义 一律 fail-closed。cloud_attacker / cloud_web+search / 运行时新装 server 全部拿不到 豁免。DEF 因此在代付两条分支都置位(ARM 仍只在 kill-switch 分支,成对不变式不变)。 Major —— 「继承来的同名条目一并抹掉」不成立: - R5 只删继承的 OPENCODE_CONFIG_CONTENT 那一份,而 mergeDeep 里缺键不会删除 global / OPENCODE_CONFIG / 项目 / managed 先前来源的定义。改为写 WITHHELD_CLOUD_MCP 中和条目 (type=remote + http://127.0.0.1:1/… + enabled:false),靠 later-wins 标量覆盖逐字段 压过继承定义;ext 确认装载后由 installCloudMcp() 整条替换。 - injectMcpDefaultDeny 里写死的 "cloud" 治理名删除 —— 名字不是治理凭据;治理集改为 「alpha.jsonc 里真有的 ∪ 本轮注入面真放进去的」。 证据(全部真实链路): - ext 单测新增 R6 六个绕过构造 + 端点身份核验组; - alpha-mcp-websearch-gate.test.ts 加真配置 + 真 ext 装载的 cloud_attacker 回归; - 新增 alpha-cloud-mcp-multisource.test.ts:真 Config.Service 多源加载(真 Global.Path.config / Flag.OPENCODE_CONFIG / 项目文件 / managed 目录),四来源各带 正向对照,另有真 MCP lifecycle + 真 HttpApi 的「中和后连不上、远端零请求」。 诚实登记(未闭合,已写进 ADR 与测试):managed 目录与 MDM 托管偏好排在 OPENCODE_CONFIG_CONTENT 之后能覆盖回去(root/管理员通道);同名 POST /mcp add 替换 已连客户端后 ext 分辨不出来(上游不暴露活的 server 定义)。 ADR-009 / ADR-035 宣称同步收窄:本票保证 = alpha 治理的云 server(按定义身份识别)+ alpha 自有的本地 websearch 工具;用户自配的第三方 web-search MCP **不在**保证内, owner 复核标记保留但不再写成已实现。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
R7 判 NOT-MERGE 的唯一 Major:MCP 治理归属快照存在 ext 的模块级 `let`, 而插件模块由 Bun 动态 import 缓存(一个引擎进程一份),Plugin/MCP 状态却 按 directory 建实例 —— 后跑的实例覆盖先跑的。实测:实例 A 记录正常 `cloud` 后 `cloud_web_search` 放行,实例 B 只多记一个 foreign `cloud_web`,A 的同一 个权威云工具立刻被误拒(跨项目串扰,违反本票对治理云 server 的保证与 AC4)。 - 归属改存**每个 AlphaExt 实例自己的闭包**,在该实例全部配置合并完成之后 (项目 alpha.jsonc 合并之后、config 钩子的 finally 里)才算,并显式传给 该实例的 tool.execute.before;模块侧删掉 `recorded` 与 recordMcpOwnership, 只留纯函数 computeMcpOwnership,判决函数默认实参恒为 UNVERIFIED_MCP_OWNERSHIP(没传 = 豁免不给);合并中途抛错同样倒向它。 - 隔离回归:两个并存 directory 的**真** AlphaExt 实例(同一个被缓存的模块 import 两次),证明 B 的 foreign server 改不动 A 的判决,两个方向都验。 修复前该用例复现误拒。 - out-of-round Minor:分类判据 ②(search + 具名搜索引擎词)实现按「词元集合 同时出现」判,而注释与 ADR 写的都是「相邻」—— 改**实现**为真正的相邻判定, `brave_translate_and_search` / `internet_archive_search` 不再被误杀(AC4 方向)。 - 措辞改准:中和条目压的是**连接控制字段**(type/url/enabled/oauth),不是 「逐字段完整覆盖」—— 真实 mergeDeep 探针显示继承的 headers/timeout 会留下; URL 已是不可用 loopback,故判决不变,只改宣称。ADR-009 同步登记七次收口。
This was referenced Jul 27, 2026
jinjunnn
added a commit
that referenced
this pull request
Jul 27, 2026
#642 的前提「ADR-009 仍是 #639 之前的版本」不成立:PR#639 自己就改了这份 ADR (+88 行,R3–R7 全部收口在内),AC 要求的六项记载与裁决 (a)/(b)/(d) 均已在册。 逐条核对后剩下的是两处真缺口。 一、裁决 (c) 声明的闸门不存在,且它守的也不是这条路。 「以禁用 provider 棘轮(断言打包端 provider 目录不含 opencode)守死」——按 forbidden-provider 与 provider 目录集合两个方向普查全仓测试,零命中。它是被写进 ADR 与 E7 基线的一处假闸门:声明了保证,没有任何可执行判据钉住它。 即便补上,它守的也是错的层。注入面把用户自配 provider id 无条件并回硬白名单: alpha-models.ts:115-117 遍历 readUserProviderIds()(ext-config.ts:784,取 opencode.jsonc 的 provider 键,零过滤)后 enabled.push(id)。用户写一条 {"provider":{"opencode":{…}}},opencode 即进 enabled_providers, webSearchEnabled(ProviderV2.ID.opencode, …)(registry.ts:58)恒真 —— 与 4 个 keyless flag 是否被 force-0 无关。「恒为死码」是错的。 主权判决不受影响,因为保证本来就不在注册闸上:执行面撞裁决 (b) 的传输层最终闸 (ALPHA_LOCAL_WEBSEARCH_DENY,mcp-websearch.ts 的 call() 与 core 的 callMcp() 各自第一句),调用被拒且零出网。与已登记的残留同类 —— 能力真关,可见性 cosmetic。 裁决:不补棘轮(守错层),不过滤该 id(登出/BYOK 自带 Zen key 是决策 A 明示允许的 形态,过滤会误伤);(c) 从「死码 + 棘轮」降级为「注册面可达、执行面被传输闸兜住」, 按现状诚实登记。「无需为它 patch 上游 registry.ts」这一半仍成立。 二、frontmatter amended 停在 2026-07-25,而正文已含 07-26 的 R3–R7 收口段。 E7 基线按其既有的「勘破更正」体例加同一个块,并声明本稿凡出现该表述之处一律以 该块为准(§3a、票 3、票 3b、票 5 共 7 处),不逐处改写。 本地闸全绿:3077 pass / 0 fail、docs 链接闸绿。 Fixes #642 Co-authored-by: jinjunnn <slmbaovanetti99@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Jul 27, 2026
jinjunnn
added a commit
that referenced
this pull request
Jul 27, 2026
…648) * fix(e7): readBoundedBody 读全响应体 —— 修 runForEachWhile 只读首个 chunk 的静默截断 `Stream.runForEachWhile` 只消费第一个 chunk 就停,即使谓词恒返回 true。三种读法实测: runForEachWhile 1 chunk / 4,090 字节;runForEach 3 chunks / 18,063 字节;response.text 18,034 字符。唯一的生产调用方 `call()` 于是拿到截断的 JSON,登出态 web search 真调报 「invalid response … Unterminated string in JSON」——上游返回的其实是成功结果。 引入点 commit d341b20(PR #639,落地 #489 的有界读取要求);该 commit 之前是 `yield* response.text`,读全、无此缺陷。为了修 #223 R2 Major 6 的 DoS 上限,把「读全」 这条正确性弄丢了,并且发了版。 修法与 `tool/read.ts:146` 同构:`runForEach` 读全 + tagged error 在触限那一刻中止上游流。 不能天真换成裸 `runForEach` —— 那会读完整条流再截,把 R2 的 DoS 洞放回来。 `MAX_BODY_BYTES` 仍是与块大小无关的硬限,`truncated` 语义与后缀不变。 类级防复发(本仓第二次踩同一个 API,第一次的教训只写在 read.ts:143 的注释里, 对正在写另一个文件的人不可见 —— 注释不是闸门): 新增 `packages/ui-mac/src/main/stream-read-hygiene.test.ts`,全仓源码禁 `Stream.runForEachWhile`, 并自带覆盖面自检与自我变异,避免成为空闸门。 行为闸落在 packages/opencode/test,而该目录以前一条都没进过 alpha CI(那份文件抬头早已 把这条盲区写下来)。补一个 CI 步骤只跑 alpha 自有的那份 web search 闸门测试。 Fixes #647 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(e7): 两条防复发闸都是假的 —— 枚举补全 + CI 断言真跑了用例 codex 第 1 轮 Major ①/②。修复本身(readBoundedBody)未动。 ① stream-read-hygiene.test.ts —— 空闸门 + 覆盖面自检假绿 手写 walker 只进 `packages/<一级目录>/src`,却表述为「全仓生产源码」。实际漏掉 packages/console/*、packages/stats/*、packages/sdk/js 三个嵌套 workspace 共 353 个生产 文件;在 packages/stats/server/src/ingest.ts 写违规代码可完全绕过,而当时的自检 (扫到 >200 文件 + 两个事故锚命中)仍然全绿 —— 它只证明「扫到了一些文件」,不证明「没漏」。 改为 `git ls-files --cached --others --exclude-standard`(可证明完备,且覆盖尚未 add 的 新文件)。覆盖面自检改成「漏扫可观测」:钉住根 package.json 的 workspace 通配符清单 (新增 workspace 根即红)+ 断言每个嵌套 workspace 根都真有文件进入集合 + 断言 packages/stats/server/src/ingest.ts 这个具体绕过点在集合里。抬头措辞改成守住什么写什么, 并显式列出守不住什么。 ② alpha-ci.yml —— 零用例默认放行 `bun test` 对「文件被清空 / 用例条件注册成零条」打印 `Ran 0 tests` 并**退出 0**(已实测)。 新步骤改为解析实际通过数并钉下界 40(当前 45),低于即红;bun 非零退出仍单独判红。 * fix(e7): 闸门文件被删除不再静默放行 —— 用例数下界收口成共用脚本 第三种同类假绿:`bun test src` 对「测试文件被删除」默认放行。删掉 stream-read-hygiene.test.ts 后 ui-mac 跑 3077 条全绿、CI 全绿,类级闸就此消失且不自愈。刚用一整轮确立「一个能被静默删除 的闸门不是闸门」,不能同时交付一个能被静默删除的闸门。 把「断言实际执行的用例数」抽成 scripts/bun-test-floor.sh,三处共用同一个机制而不是三份拷贝 (拷贝会各自漂移,那是下一次漏掉的成因)。 两层下界,各管各的 —— 单靠整套 suite 的地板抓不到「少了一个文件」: · ui-mac 整套 suite 地板 3000(当前 3085):抓灾难性丢失(加载失败/成片删除/runner 配错)。 刻意留大余量 —— 会随正常增删漂移的阈值早晚被调松或删掉,那又是一个自毁的闸门。 实测:删掉类级闸后本步 3077 条**仍然绿**,所以它抓不到单文件删除,不能靠它。 · 类级闸单独点名,下界 6(当前 8,留出登记项增减的余量):这一层才抓单文件删除。 实测:文件删除 → bun exit 1 → 红;文件清空 → bun exit 0 但 0 条 → 下界判红;放回 → 8 条绿。 * fix(e7): 整类收口 —— contracts-consumer / ext 两步同样是零用例默认放行 自查第四种时发现:我上一条回复答了「无」,是错的。`test` job 里还有两步只看退出码 —— `bun test (contracts consumer fixtures)` 与 `bun test (ext)` —— 与刚修的三步是**同一类**。 实测(把 ext 全部 10 个测试文件清空): 裸 `bun test` → `Ran 0 tests across 10 files`,**exit 0**,原步骤全绿 带下界的新步骤 → exit 1 两步一并接到 scripts/bun-test-floor.sh(下界 15 / 100,当前 19 / 132)。 只修被点名的实例、留着同类的其余实例,正是本 PR 在讲的那个故事。机制已经有了,整类一起收。 同时放宽脚本参数检查:允许只给 floor + workdir(整包跑,等价于原来的裸 `bun test`), 不再强制第三个过滤参数。 * fix(e7): 闸门文件全量类扫 —— 21 个逐个点名 + 登记簿完备性闸 上一轮我在 ui-mac 上实测出「整包地板抓不到删掉一个 N 条用例的文件」,却只把这条规律应用了 一次:ext 只加了地板 100,而 ext 排除 REQ-062 drift lock 后还剩 106 条 —— 删掉那个闸门文件 CI 静默全绿。同一个类,修了一个实例。这一轮做全量。 闸门文件的判据(写进 scripts/gate-files.tsv 抬头):删掉它就移除某条**具体保证**,而不只是 减少覆盖率;操作性signature = 它断言的是自己模块之外的东西(仓库源码文本、vendored/生成的 fixture、上游文件、钉住的清单),于是没有别的测试会因为它消失而变红。 跨五个 CI 步骤扫全,登记 21 个:契约哈希锁、REQ-062 prompt drift lock、云 websearch kill-switch、REQ-100 skill 注入门、seed 快照守卫、REQ-012 上游 DOM 锚点、ADR-027 seam 存活、 对比度/接管件/cards/permission 挂载/settings surface/reduced-motion/route authority 六个棘轮、 alpha-web 契约 fixture、契约横幅接线、#647 类级禁令与 web search 行为闸。 逐个删除实测:21/21 删除即红、放回即绿。 三件配套: · scripts/gate-files.tsv —— 登记簿,每行带下界与「这道闸保证什么」。 · scripts/assert-gate-files.sh —— 逐个跑 + 逐个判下界;登记簿被清空(<15 条)也判红。 · packages/ui-mac/src/main/gate-file-registry.test.ts —— **完备性闸**:凡文件名命中仓库 闸门命名习惯(ratchet/lock/anchors/contract/copies/snapshot/drift/hygiene/rebrand/seam/ coexistence/census)的测试文件,必须要么登记、要么在 NOT_GATES 里写明为什么不是。 这一条才是「别再只应用一次」的机制本身;它自己也在登记簿里。实测新增未登记文件即红。 分层判据写进了 tsv 抬头、脚本抬头与 workflow 注释三处,并各自附上实测数字 (ui-mac 3077≥3000 全绿、ext 106≥100 全绿),免得下一个人再以为地板就够了。 * fix(e7): 委派规则 —— 主判据被委派出去时受托方也必须在册(登记簿 21→30) owner 裁决 route-deep-link-consumer.test.ts 必须登记,理由是我自己的判据: route-authority-ratchet 抬头写着「THE PRIMARY JUDGEMENT IS NOT IN THIS FILE」,那么删掉受托方 保证就没了,棘轮只守住「源码里有那段文字」—— 按本仓定义就是假闸门。而我判它不登记的理由是 「不命中命名词」,那正是问题所在:按名字枚举与当初只扫一级目录的 walker 是同一种缺陷。 做法(选显式列 + 发现绊线,不选自动解析抬头): · 权威 = 登记簿新增 `delegates_to` 列,**默认拒**:每行必须显式填,没有委派写 `-`,留空即红; 且每个受托方必须也在登记簿里。自然语言的委派说法太多(「真实判据见」「行为层闸门在」 「移至」「由 X 断言」…),按措辞枚举与按文件名枚举是同一种缺陷,不能当权威。 · 发现绊线 = 闸门文件正文里出现的**任何**其它测试文件都必须已被分类(已登记 / NOT_GATES / REFERENCED_BUT_UNREGISTERED 三选一)。不理解语义,只要求「提到了就表态」。 用绊线核完 21 个抬头,**另外找出 7 处**未登记的委派关系(一层二层都没发现): websearch-copies → core/alpha-websearch-sovereignty(运行时半场,此前不在任何 CI) → sidecar-env、alpha-config-injection(「真实判据见」) gen-skill-paths → ext-receipt-v2(「decoder 强度用例移至」) takeover-coexistence → permission-single-surface(「行为层闸门在」) permission-mount → permission-single-surface route-authority → route-deep-link-consumer、route-upstream-shape 二阶闭包再找出 2 处:alpha-config-injection → sidecar-ready-message(纯文本锚曾被变异绕过)、 alpha-models。逐层登记到不动点。 登记簿 21 → 30 条(含首次纳入 CI 的 packages/core 那份主权闸运行时证据)。 删除实测 30/30 全 PASS。委派规则绕过实测:受托方未登记 → 两条断言各自转红; delegates_to 留空 → 测试与 shell runner 两侧都转红。 未登记但已表态的 4 处写进 REFERENCED_BUT_UNREGISTERED,各带理由与跟踪票: 三个 opencode 云 MCP/网关测试归 #649 评估(owner 已指示不在本 PR 纳入), 两个上游 websearch 测试由 north-star 守卫保护、alpha 刻意不接管。 --------- Co-authored-by: jinjunnn <slmbaovanetti99@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jinjunnn
pushed a commit
that referenced
this pull request
Jul 28, 2026
#223 AC5「packaged 真调用与 keyless fallback 均有证据」的取证需要 owner 本人的登录态, 只有取证之前的一切可以先做完。本 PR 交付那一切: - 打包应用重打并真机启动验(alpha@94a76b669;装机版 app.asar 的 sha256 钉进探针, 在错的构建上跑出来的绿会被第一条判据挡住)。 - 一个两相位探针:默认 = 登录态,`--keyless` = 登出态,**各自对错误的登录态 fail-closed**。 故意不做自动识别 —— 会自己切相位的探针永远报不出「未登录,无法取证」。 每项自带运行前定好的判据,输出机器可读 JSON;造不出来的失败态记 `not-producible` + 理由, 不记成绿。探针零密钥:用的每个凭证都是打包应用自己写进 alpha-secrets 的,写盘前经 redact。 - 证据骨架:逐项判据表(登录态那半留空待 owner)、复现步骤、边界。 准备阶段已实跑出两件事: 1. **未登录自检**:登录态相位 blocked + exit 2,零证据产出(results/logged-in-*.json)。 2. **一个阻断项**:登出态 keyless 真调在本构建上是坏的。`readBoundedBody` 用的 `Stream.runForEachWhile` 在谓词恒真时仍只读第一个 chunk(实测 4,090 / 18,063 字节), 于是任何多 chunk 的搜索响应被截断 → `invalid_response`。#639 之前该处是 `response.text`, 属那一刀带进来的回归;本仓 read.ts:143-145 早已记过同一个 API 的雷。 修复归一张 CODE 票,不在本 VERIFY 票里动源码。 另记两项待裁决:402 的采集范围(Issue 正文与基线票 6 的 2026-07-25 更正相反), 以及云工具在引擎里的真实 id 可能是 `cloud_cloud_web_search`(MCP catalog 会拼 server 名前缀)。 Refs #643
jinjunnn
added a commit
that referenced
this pull request
Jul 28, 2026
* docs(verification): #643 E7 打包版取证准备 —— 探针 + 证据骨架 + 一个阻断项 #223 AC5「packaged 真调用与 keyless fallback 均有证据」的取证需要 owner 本人的登录态, 只有取证之前的一切可以先做完。本 PR 交付那一切: - 打包应用重打并真机启动验(alpha@94a76b669;装机版 app.asar 的 sha256 钉进探针, 在错的构建上跑出来的绿会被第一条判据挡住)。 - 一个两相位探针:默认 = 登录态,`--keyless` = 登出态,**各自对错误的登录态 fail-closed**。 故意不做自动识别 —— 会自己切相位的探针永远报不出「未登录,无法取证」。 每项自带运行前定好的判据,输出机器可读 JSON;造不出来的失败态记 `not-producible` + 理由, 不记成绿。探针零密钥:用的每个凭证都是打包应用自己写进 alpha-secrets 的,写盘前经 redact。 - 证据骨架:逐项判据表(登录态那半留空待 owner)、复现步骤、边界。 准备阶段已实跑出两件事: 1. **未登录自检**:登录态相位 blocked + exit 2,零证据产出(results/logged-in-*.json)。 2. **一个阻断项**:登出态 keyless 真调在本构建上是坏的。`readBoundedBody` 用的 `Stream.runForEachWhile` 在谓词恒真时仍只读第一个 chunk(实测 4,090 / 18,063 字节), 于是任何多 chunk 的搜索响应被截断 → `invalid_response`。#639 之前该处是 `response.text`, 属那一刀带进来的回归;本仓 read.ts:143-145 早已记过同一个 API 的雷。 修复归一张 CODE 票,不在本 VERIFY 票里动源码。 另记两项待裁决:402 的采集范围(Issue 正文与基线票 6 的 2026-07-25 更正相反), 以及云工具在引擎里的真实 id 可能是 `cloud_cloud_web_search`(MCP catalog 会拼 server 名前缀)。 Refs #643 * docs(verification): #643 重打包到 e578e00 —— keyless 阻断项解除,登出态相位 10/10 绿 上一份装机产物(`94a76b669` / `8706d0c4…`)早于 #648 的 `readBoundedBody` 修复, K1.5(登出态 keyless 真调)在其上必红 —— #643 的 AC2 不可能转绿。在当前 alpha HEAD `e578e00ae` 上重跑 `ship:mac` 并重新装机,证据目录的指纹与 `probe.ts` 的 `PINNED_ASAR_SHA256` / `PINNED_COMMIT` 同步换成新产物 (`60589c59c58e44ac0daede93fc7397a8a04365f5345eac4312e205a0d8f48e44`)。 真机启动已验:main / renderer / GPU / NetworkService / NodeService 五进程齐, CDP 列出 `oc://renderer/index.html` page target(窗口真开), `server ready { url: 'http://127.0.0.1:50360' }`,crash.log 无崩溃。 本仓两个打包坑(source-only contracts-consumer 留原始 .ts、eager ajv `new Function` 撞 renderer CSP)均未复现。 阻断项解除,用两条独立判据确认「打包版里真的生效」而非「源码合了就假定」: 1. 解包 app.asar,`out/main/chunks/node-*.js` 的 `McpWebSearch.readBoundedBody` 确为 `Stream.runForEach` + `catchTag("BodyCapReached")`; 2. 真机跑 `--keyless`:K1.5 `completed`,拿到完整多 chunk 的真实搜索结果,exit 0、 10/10 必需项通过。 两条反向 fail-closed 自检也在新产物上复验:登出态跑登录态相位 → blocked + exit 2; 登录态跑 `--keyless` → blocked + exit 2。两者都只记前置、零证据产出。 未改动源码,未改动 `docs/verification/` 下任何其它目录;旧产物的原始记录 (`*20260727T0952*.json`)保留不动。登录态那一半仍待 owner 本人执行。 Refs #643 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(verification): #643 402 按裁决改为「采」+ MISMATCH 判读方法 owner 2026-07-27 在 #643 上裁定 402「采」(多采一项没有害处,漏采要再叫 owner 一次), 但上一轮探针仍按 Issue 正文走 out-of-scope —— 裁决没落进代码。本次补上。 **P3.7 从「跳过」改成真打的判据**,且不预设结论:发一次带真 bearer 的 `POST /v1/tools/web_search`,按运行前定好的规则判读 —— 402 ⇒ pass;200 ⇒ `not-producible`(账户被预授权通过,这本身就是「今天产生不了」的 可观测证据,带实测 balanceFen/walletUsedFen/plan 作理由);其它状态 ⇒ fail(LOUD)。 不记成绿,也不静默跳过 —— 与 P3.4/P3.5/P3.6 同一套处置。 可产生性已按平台源码判定(alpha-platform `packages/gateway/src/worker.ts`): - 臂 A(per-job 预算耗尽)**桌面端不可达** —— 需 `auth.via === "job"` 的 job token (`lib/tenant-auth.ts:114-123`);桌面端拿的是 route-purpose 绑定 JWT,jobId 恒空 ⇒ `perJobPrecall` 直接 pass/enforced:false。与 P3.4 同源。 - 臂 B(`accountPreauth` 拒绝)只在账户余额+会员额度双空时触发;估价是路由常量, 客户端无调价杠杆。把账户打空是破坏性计费变更,且会连带打红同轮的 P2.1/P2.2/P2.3 —— 402 证据与 AC1/计费证据不可能在同一账户形态下同时取到。**owner 无需为 402 做额外操作。** 映射本身由 L1 覆盖:`packages/opencode/test/tool/alpha-websearch-failure.test.ts`。 `#643` 正文冲突已消:out-of-scope 与退出条件两处按裁决改成与基线票 6 的 2026-07-25 更正一致(须含 402,产生不了则记 not-producible)。 README §7 补上 **`MISMATCH` 的判读方法** —— 此前只记录不解读,等于把判断留给一个没有 上下文的人。现在写清:出现 MISMATCH 意味着所有按 `cloud_web_search` 字面量下的闸 **当前是空闸门**(盯着一个引擎从不注册的工具名),并逐个列出要重核的闸(permission deny / ext `tool.execute.before` / catalog 拼名)、下错了各会怎样、以及判据必须是「用真实 id 跑一遍确认真的命中」而不是 grep 到字面量。 证据同步重跑,使committed 证据由 committed 探针产出: 登出态相位在新产物上独立两遍均 10/10(K1.5 不是侥幸);两条反向 fail-closed 自检复验通过。 本地闸门:north-star UPSTREAM 命中 0;docs gate 11 个相对链接全解析。 未改源码,未改 `docs/verification/` 其它目录,未动应用登录态(保持登出)。 Refs #643 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(verification): #643 owner 登录态取证结果(打包版 e578e00) owner 亲自登录后跑完登录态相位。23 项判据:AC2(登出态 keyless)已在前一轮通过; 本轮登录态暴露六条必需红,其中两条是真缺陷、两条是探针判据缺陷: 真缺陷 - 云 MCP tools/call 恒 forbidden:cloud_web_search 要 purpose=model.invoke, 而桌面云通道令牌 ALPHA_CLOUD_TOKEN 恒为 cloud.dispatch(alpha-platform#131) - /mcp 传输层零鉴权,tools/list 与账户无关 ⇒ P1.3 那条 PASS 是空绿(alpha-platform#132) 探针判据缺陷(alpha-code#651) - P1.5 断的是「每个 agent 配置键有 deny」这个机制,而有效判决本来就全是 deny - P1.2 断言观测点错:引擎加载时已解析 {file:},/config 天然看不到 file ref - P3.8 判据构造上不可满足(工具已被 Permission.disabled 过滤,模型拿不到) 另:引擎侧真实工具 id 实测为 cloud_cloud_web_search(双前缀),按字面量 cloud_web_search 下的 permission 闸因此是空闸门(alpha-code#650)。 #643 不能关:AC1 待 #131 修复并手动部署后重取,AC3 待 #651 收敛判据。 --------- Co-authored-by: jinjunnn <slmbaovanetti99@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
三片
片 1 冷启动时序(Fixes #621) ——
server.ts的 force-off 判定依赖ALPHA_CLOUD_MCP_URL,而它由initAuthEnv()在 whenReady 之后写入,晚于preferAppEnv()。于是冷启动登录用户的 force-off 恒假,反而把OPENCODE_ENABLE_EXA设成"1"→ sidecar fork 时本地 keyless 与云工具双活,AC1「登录代付态云优先」根本不成立。判决改为每次 fork 前重算(与既有syncSecretFiles同纪律),并做成双向(登出 respawn 还原 keyless)。原单测在调用前手工预置该 env,恰好掩盖了真实时序。片 2 失败诚实化(Fixes #489) —— 删掉「空/nullish 结果当成功串」的伪成功路径,
Effect.orDie换成可辨的 typed failure;失败集覆盖 401/403action_forbidden/400/402/502 与任何非 2xx;structuredContent进 schema,合法零命中不再被误判为失败,200 带结构化 error 不再被当成功;body 读取与 headers 同一 timeout 且硬限 2 MiB(初版单个 3 MiB chunk 实收 3,145,728 字节)。片 3 主权闸 —— 最终判决下沉到传输层(
mcp-websearch.ts的call()与 core 的callMcp(),两者都在 ADR-035 收编面内),并把 core 的callMcp导出,使「复用传输」成为新增副本的正确写法(复用即受闸)。云工具的 kill-switch 钳在 ext 插件钩子tool.execute.before首句(alpha 自有包,零上游接管),该钩子不查任何 permission ruleset,因此后置的 agent wildcard / 持久 session permission /approved一概够不着。云 server 的注册绑定到真实装载握手(ARM+DEF 成对,且 DEF 里的 URL 身份不可由 MCP 名称伪造),ext 未确认时完整定义根本不进配置,并用一条中和条目(loopback 端口 1,不做 DNS、必然 ECONNREFUSED)压掉 global/项目/managed 各源继承来的同名定义。审计
Codex 对抗审计 8 轮到 MERGE。被推翻的实现者自陈包括:「permission deny 即 kill-switch 保证」(可被三条后置规则顶掉)、「V2 Core 的 websearch 永不挂载」(实为第二份副本,引擎 v1/v2 断层第三次现形)、「ext 未装载即 fail-closed」(
OPENCODE_PURE可让插件整体跳过而路径仍在)、「传输层是共同执行边界」(通用 Remote MCP 是第三条出口)、「继承来的同名条目一并抹掉」(只清了OPENCODE_CONFIG_CONTENT)、「所有权快照按实例隔离」(实为模块级,跨项目串扰)。诚实登记的边界:本票保证 ① alpha 治理的云 server(按定义身份识别)② alpha 自有本地
websearch(传输层闸);用户自配的第三方 web-search MCP 不在保证内,对它是尽力识别(Unicode 名、同名add替换、managed/MDM 通道均已具名登记)。ADR-009/035 的宣称已按此收窄。闸门(本地实跑,已 rebase 到
a50322e0f)test/tool+test/mcp462/0git diff --check干净顺带
scripts/alpha-check.sh自 ADR-033 起从未补 exclude 表,本地守卫一直假红、与 CI 不是 1:1 —— 本 PR 一并补齐。Fixes #489
Fixes #621