docs(verification): #643 E7 打包版取证准备 —— 探针 + 证据骨架 + 一个阻断项 - #646
Conversation
#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
上一份装机产物(`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>
9c29a4f to
337d9b3
Compare
重打包到
|
| 项 | 值 |
|---|---|
| 基线 commit | e578e00ae(alpha,工作树干净) |
sha256(app.asar) |
60589c59c58e44ac0daede93fc7397a8a04365f5345eac4312e205a0d8f48e44 |
| 构建时间 | 2026-07-27T21:31:42 -0400 |
| 应用版本 / 引擎 | 0.1.2 / 1.17.13 |
(旧:94a76b669 / 8706d0c44e17d475ab136717ba9cbbbfad4c968ae34689ed377fdecc19fb43ff。)
README.md 与 probe.ts 的 PINNED_ASAR_SHA256 / PINNED_COMMIT 已同步 —— 不换的话第一条判据会直接把 owner 挡住。
真机启动验
五进程齐(main / renderer / GPU / NetworkService / NodeService sidecar);CDP 列出 oc://renderer/index.html 的
page target(窗口真的打开);logs/20260728T013220/main.log 有 server ready { url: 'http://127.0.0.1:50360' };
同目录 crash.log 只有 crash reporter started。本仓两个打包坑均未复现 —— asar 里 contracts-consumer
没有以原始 .ts 被运行时 require(启动无崩溃),renderer.log 只有既有的 connect-src 源列表告警、无 eval 被拒。
阻断项:已解除 ✅
刻意用两条独立判据确认修复在打包版里生效,而不是「源码合了就假定」:
- 解包
app.asar——out/main/chunks/node-*.js里的McpWebSearch.readBoundedBody确为
Stream.runForEach+catchTag("BodyCapReached"),打进去的是修好的那份。 - 真机跑
--keyless—— K1.5status === "completed",输出是完整的多 chunk 真实搜索结果:
[PASS] K1.1 no alpha cloud MCP server is registered while logged out
[PASS] K1.2 the local keyless websearch is no longer denied once the platform stops paying
[PASS] K1.3 a BYOK tool-calling model is available to drive the keyless call
[PASS] K1.4 the local keyless websearch tool is offered to the model again
[PASS] K1.5 a real keyless web search actually returns results
[PASS] K1.6 if the keyless call fails, the failure reaches the model as a discernible error
checks: 10 required-failures: 0 EXIT=0
K1.5 实测 outputHead:Title: 7dad00e feat(opencode): Phase 4 … URL: https://github.com/… —— 真结果,不是截断 JSON。
驱动模型是 BYOK 的 deepseek-byok / deepseek-v4-flash(登出态本来就该走自己的钥匙)。
两条反向 fail-closed 自检也在新产物上复验
| 自检 | 结果 |
|---|---|
| 登出态跑登录态相位(默认) | [BLOCKED] P0.4 + EXIT=2,只记 P0.1–P0.3 前置,零证据产出 |
登录态跑 --keyless |
[BLOCKED] K0.4 + EXIT=2,同样零证据 |
这条闸是防止取证脚本自欺的关键,重打包后两个方向都复验过。
本地闸门
- north-star:diff 仅
docs/verification/2026-07-27-e7-packaged-live/**,UPSTREAM_PATHS命中 0 ✅ - docs gate:
python3 scripts/check-doc-links.py→ 10 个相对链接全解析 ✅ - typecheck / unit:docs-only 改动,无 tsconfig 覆盖
docs/(pre-push 钩子亦全绿)
机器当前状态 / owner 还需要做什么
应用已装新产物、ALPHA_CDP=1 已开、当前处于登出态(为跑 keyless 相位而登出,走的是应用自己的
「退出登录」路径;BYOK 钥匙没动)。owner 只剩登录 + 跑一条命令:
# ① 在应用里登录(平台代付模式),等模型目录出来
# ② 登录态取证
cd ~/app/alpha-code && git checkout verify/643-e7-packaged-live && git pull
bun docs/verification/2026-07-27-e7-packaged-live/probe.ts若应用被关掉了,先 pkill -f "/Applications/alpha-code.app" ; sleep 2 ; ALPHA_CDP=1 open -a /Applications/alpha-code.app。
仍待 owner 裁决的两项没变:402 采不采(#643 正文 out-of-scope vs 基线票 6 的 2026-07-25 更正)、
以及云工具在引擎侧的真实 id(P1.3/P1.4 记录实测值、不假定)。
未合并、未关闭 #643 —— 登录态那一半的证据还没采到。
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>
402 裁决已落进代码 +
|
| 观测 | 记法 | 含义 |
|---|---|---|
| HTTP 402 | ☑ pass(required) |
真拿到 402 证据 |
| HTTP 200 | not-producible(非 required) |
账户被预授权通过 —— 这本身就是「今天产生不了 402」的可观测证据;JSON 带实测 balanceFen/walletUsedFen/plan 作理由 |
| 其它状态 | ☒ fail(required) |
意外状态 LOUD,先查清再接受这一轮 |
不记成绿,也不静默跳过 —— 与 P3.4/P3.5/P3.6 同一套处置。
2. 可产生性:按平台源码判定,结论是 owner 无需额外操作
依据 alpha-platform packages/gateway/src/worker.ts 的 webSearchHandler,只有两条臂能出 402:
| 臂 | 桌面端可达? |
|---|---|
A. per-job 预算耗尽(perJobPrecall → kind:"over") |
不可达。需 auth.via === "job" 的 job token(lib/tenant-auth.ts:114-123,JOB_TOKEN_SECRET 签、claims 带 job_id);桌面端拿的是 route-purpose 绑定 JWT(via:"jwt"),auth.jobId 恒空 ⇒ perJobPrecall 直接 pass/enforced:false。与 P3.4 同源:桌面端铸不出那个形状的凭证 |
B. accountPreauth 拒绝 |
仅当账户余额与会员额度双空。估价是路由常量 BILLABLE_ROUTES[…].estimatedCostUsd,请求体只有 {query,max_results},客户端没有任何调价/调额杠杆 |
所以:没有可以临时切的开关,owner 不需要为 402 做任何额外操作。把账户打空去凑 402 是对 owner
计费状态的破坏性变更,而且会连带让同一轮的 P2.1/P2.2(真调)与 P2.3(计费)一起失败 ——
402 证据与 AC1/计费证据不可能在同一个账户形态下同时取到。探针会照实记录当天的账户形态。
映射本身(两条臂 → payment_required)由 L1 覆盖:packages/opencode/test/tool/alpha-websearch-failure.test.ts。
它发生在 P2.3 量完钱之后,不影响 P2.3 的差分;但读账本会看到 3 笔而非 2 笔,第 3 笔就是它。
3. #643 正文冲突已消
out-of-scope 与 退出条件 两处已按裁决改成与基线票 6 的 2026-07-25 更正一致
(「须含 402 项」;产生不了则记 not-producible + 实测账户形态 + L1 覆盖位置)。
下一个读票的人不会再撞见这处矛盾。
4. MISMATCH 判读方法 —— 写进 README §7
此前只记录不解读,等于把判断留给一个没有上下文的人。现在写清:出现 MISMATCH 意味着所有按
cloud_web_search 字面量下的闸当前是空闸门(盯着一个引擎从不注册的工具名),并逐个列出:
| 要重核的闸 | 位置 | 下错了会怎样 |
|---|---|---|
| permission deny(云优先时抑制本地 keyless) | packages/ui-mac/src/main/cloud-web-search.ts |
抑制不生效或误伤,P1.5/P1.7 的绿变成假绿 |
ext 的 tool.execute.before 钩子 |
packages/ext/src/ |
kill-switch(#223 AC4)拦不住真实工具名 = 关不掉 |
| 引擎侧拼名 | packages/opencode/src/mcp/catalog.ts:117-119 |
模型看到的工具与闸盯的工具不是同一个 |
重核判据不是「grep 到字面量」,而是用真实 id 跑一遍这三个闸并确认真的命中。探针的 MISMATCH
note 也改写成同样的口径,并指向 README §7。
5. 证据重跑 + 本地闸门
committed 证据由 committed 探针产出:登出态相位在新产物上独立跑了两遍,均 10/10
(K1.5 不是一次侥幸);两条反向 fail-closed 自检(登出态跑登录态相位 / 登录态跑 --keyless)
均 blocked + EXIT=2、零证据产出。
- north-star:diff 仅
docs/verification/2026-07-27-e7-packaged-live/**,UPSTREAM_PATHS命中 0 ✅ - docs gate:11 个相对链接全解析 ✅
owner 的最终步骤
应用已装新产物(e578e00ae / 60589c59c…)、ALPHA_CDP=1 已开、保持登出态。
只剩登录 + 一条命令,402 不需要任何额外动作:
cd ~/app/alpha-code && git checkout verify/643-e7-packaged-live && git pull
# ① 在应用里登录(平台代付模式),等模型目录出来
# ② 登录态取证 —— 一条命令跑完(含 402 判据,无需额外操作)
bun docs/verification/2026-07-27-e7-packaged-live/probe.ts若应用已被关掉,先:
pkill -f "/Applications/alpha-code.app" ; sleep 2
ALPHA_CDP=1 open -a /Applications/alpha-code.app未合并、未关闭 #643 —— 登录态那一半的证据还没采到。
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 收敛判据。
本 PR 只交付取证准备;实际取证待 owner 登录后执行。不要合并到取证完成为止(或按 owner 判断先合准备、再补结果)。
Refs #643 — 故意不写
Fixes:#643 的证据还没采到。为什么
#223 的 AC5「packaged 真调用与 keyless fallback 均有证据」需要已登录的真机,而登录只能由 owner 本人完成。
所以这一票能提前做完的只有「取证之前的一切」,做到 owner 一坐下就能一次跑完。
交付
ship:mac重打并真机启动验(alpha@94a76b669)。装机版app.asar的 sha256钉进探针 —— 在错的构建上跑出来的绿是假绿,第一条判据就把它挡住。
(装机版此前是 2026-07-25 的
514ae7438,早于 fix(e7): 云搜索主权闸下沉传输层与插件钩子 —— 修冷启动时序、失败诚实化、kill-switch 真能关 #639,不能用。)docs/verification/2026-07-27-e7-packaged-live/probe.ts—— 两相位,各自对错误的登录态 fail-closed:blocked+ exit 2 + 零证据产出。--keyless= 登出态取证;仍在登录态 →blocked+ exit 2。criterion);造不出来的失败态记not-producible+ 理由 + L1 覆盖位置,不记成绿。<userData>/alpha-secrets/的,运行时读、写盘前redact()。docs/verification/2026-07-27-e7-packaged-live/README.md—— 被测件指纹、复现步骤(可复制粘贴)、逐项判据表(登录态那半留空)、边界(不证明什么)。
准备阶段已实跑出来的两件事
blocked+ exit 2,零证据(results/logged-in-*.json)。readBoundedBody用的Stream.runForEachWhile在谓词恒真时仍只读第一个 chunk(脱机实测同一请求:runForEachWhile4,090 字节 /runForEach18,063 字节 /response.text18,034 字符),多 chunk 的搜索响应因此被截断 →invalid_response。fix(e7): 云搜索主权闸下沉传输层与插件钩子 —— 修冷启动时序、失败诚实化、kill-switch 真能关 #639 之前该处是
const body = yield* response.text,属那一刀带进来的回归;packages/opencode/src/tool/read.ts:143-145早已记过同一个 API 的雷。不在本 VERIFY 票里改源码 —— 修复归一张 CODE 票,[E7][VERIFY] 打包真调 + keyless 兜底 + 计费/失败证据(L2/RC) #643 的 AC2 在修好并重新打包前不可能转绿。
待 owner 裁决
(402 已是可采集的真实失败态,须含)。探针按 Issue 正文执行,冲突同时记进 JSON。
cloud_+ 远端名),而实测远端名已经是cloud_web_search⇒ 引擎侧可能是cloud_cloud_web_search(全仓 grep 零命中)。探针记录实测值不假定;若不一致,所有按字面量下的闸需另开 CODE 票重核。
文档影响
docs/verification/新增一个带日期的目录(证据类,maintain-repository-docs契约)。未改动
docs/verification/下任何既有目录。无状态/优先级写进 Markdown。本地闸门
docs/verification/2026-07-27-e7-packaged-live/**,零UPSTREAM_PATHS命中 ✅python3 scripts/check-doc-links.py→ 7 个相对链接全解析 ✅docs/,不受影响🤖 Generated with Claude Code