Repository navigation
v3.141.592655
v3.141.592655
自 v3.141.592654 以来的 35 个提交 / 76 个文件(git rev-list --count v3.141.592654..d69e767;git diff --shortstat v3.141.592654..d69e767:6,303 增 / 146 删)把 wave-4–7 合入:图片能力位真正生效、#273 的 tool_result 顺序修复(本版唯一有意 wire 变化)、账号真相与计费历史、上游 4xx 归类、support bundle 凭据脱敏,以及三份诊断/文档基础设施。上游 proto 形状未变;客户端可见变化只有两类:声明 supports_images:false 的模型不再收到图片(改为计数剥离;被剥空的终端用户轮回 400),以及原生工具历史里乱序的 tool_result 被移动到它的调用之后。
版本号:π 的十进制是 3.141592654,本版是下一个 patch 号,语义化版本写作 3.141.592655(tag
v3.141.592655,本文件RELEASE_NOTES_3.141.592655.md)。分支名release/3.141.592655,代码祖先d69e767。
1. supports_images 从“广告”变成门
修复前 /v1/models 从实时目录宣告 supports_images,但没有任何请求路径读它(C3-01):图片被转发给同一台服务器声明为纯文本的模型,上游回 106 字节 invalid_argument,代理再把它误判成 503(§4)。实测目录 240 行:224 true / 0 false / 16 静默——所以“缺失”必须继续按未知转发,只有显式 false 才剥离。
- 门放在共享的 DEVIN_CONNECT 入口(chat、responses、messages 都经过它)。剥离而非 400:保住文本轮,不把一个没有活模型验证过 false 分支的位变成硬失败。每个被丢的块计
images_dropped_capability并 warn 点名 selector。 - P2-2(wave 7):能力位此前读的是池级 union(first-writer-wins),同一 selector 下账号 A
true、Bfalse时,谁先同步谁决定两个账号的行为。现在getConnectSelectorImageCapabilityForAccount(accountId, selector)读该服务账号自己的目录行,三态(true/false/未知→转发);env-token 回退没有池账号,保留 union。两种同步顺序都有测试,且断言 union 在每种方向上都持有相反位。 - P2-3:
supports_images:false把一个终端图片-only 用户轮剥成content: []时,builder 会编码空 user 帧(可能被合并掉,留下更早的陈旧提示)。现在剥离后重新用hasAnswerableUserContent检查终端非 system 消息,空则400 invalid_request_error、code=image_only_turn_unsupported并点名supports_images;更早的图片-only 轮被剥空不影响 tool-result 续轮(b2b396a的控制用例)。 - 测试:
test/connect-image-capability-gate.test.js10/10、test/connect-image-capability-account.test.js4/4;突变 W4-M3/M4/M9/M10/M11/M12 全部被捕获。
2. #273:乱序 tool_result 移到调用之后(唯一有意的 wire 变化)
现场复现(scratch DEVIN_CONNECT,claude-haiku-5-5-medium,temperature:0):[user, result(a), call(a), user] 在解码后的 wire 上是 [1,4,2,1],上游 502 invalid_argument;有序控制 [1,2,4,1] 回 200 ALPHA-42。丢结果仍然 502,丢调用则模型重新执行工具——所以重排是唯一既保留内容又符合已测接受形状的修法。
src/handlers/tool-emulation.js新增orderToolResultsAfterCalls():把“匹配调用在后面的” tool 消息推迟到该调用之后,保持原有结果顺序;没有移动时原样返回。normalizeMessagesForCascade在nativeStructured下、interleaveParallelToolMessages之前运行;文本模拟路径字节不变。- P2-1(wave 7):
emulateTools=false且没有声明tools时,handler 只走interleaveParallelToolMessages,绕过了上面的修复(nativeToolCall默认开,builder 仍发 #6/#7)。现在无 tools 分支在nativeFlag下先重排,H4-T8 用真实 handler 断言[1,2,4,1]。 - wire 影响:270 个比较中 12 个改变,全部是 fixture 5(
[tool, assistant, user])×normalize/strip× native/emulated × 3 环境。长度不变(native 1047 / 文本折叠 1055),非消息字段逐字节相同,消息 payload 除每个消息的 #1 uuid 外相同,角色序列4,2,1→2,4,1(native)与1,2,1→2,1,1(文本折叠)。把src/handlers/tool-emulation.js单独回退到4406ca6^后 270/270 全等——12 个差异恰好就是 #273 的重排。 - 这 12 个差异由
test/wire-expected-diffs.json逐条声明:每条带 issue、fixture/route/native/env、字节长度、重排前后角色序列。校验器test/wire-expected-diffs.js要求消息字段以外的每个字节相同、payload 去掉 #1 uuid 后多重集相同、uuid 序列本身相同、角色序列逐项匹配;没有条目的差异仍然失败,条目匹配不到差异(过期)或匹配到多个比较都会失败。比较基线包含该修复后,必须删除条目。 - 测试:
test/wire-h4-tool-pair-order.test.js8/8;test/wire-expected-diffs.test.js13/13(含“没有条目”“形状不符”“过期条目”“重复匹配”的负例);突变wire-h4-tool-pair-order.json6/6 捕获。#141的“第二条指令重放”在 proxy 层未复现(fixture 与真实 Claude Code 2.1.306 两轮会话都只各出现一次,test/wire-h5-instruction-replay.test.js钉住);#273.2的 reasoning 多帧重复只复现了行为、缺真实 Devin 客户端抓包,未改编码器。
3. 账号真相:success 可用、免费档决策、成功新鲜度、计费窗口
- #159:两个门(
isModelAllowedForAccount与getAvailableModelsForAccount)此前只认user_status;一个刚成功回答过的模型(reason:'success')会从availableModels消失,pool 被 403probe_pending。现在共享一个capabilityVerdict():not_entitled是权威否定,success是权威肯定(带新鲜度,见下),无裁决才回落静态 tier 表;UID-only 模型(如glm-5.1,enum 0)也进入列表,不只是路由。wave 4 红先行 4 条失败(14 条中);wave 7 的新鲜度用例再红先行 2 条;最终test/account-entitlement.test.js18/18。 - #149(决策,不是新增):
FREE_TIER_BASE保持['gemini-2.5-flash']。免费账号能用 GLM/Kimi 是按账号探测出来的(enum 键模型走 GetUserStatus allowlist,UID-only 走一次成功调用并持久化到capabilities),不是静态三个;把一个猜测写进基表只会把干净的 403 变成上游 529。test/free-tier-cold-start.test.js4 条钉住冷启动契约,src/models.js常量旁与两份 README 写明理由。 - P2-4 成功新鲜度:
success只在lastCheck >= userStatusLastFetched时算肯定——降级/试用过期的账号不再无限期列出并路由高级模型。刷新后的 allowlist 省略某个 enum 时写not_entitled(不再保留旧 success);但之后真正成功的一次调用会以更新的lastCheck重新建立成功。 - P2-6 计费窗口:失败行与 billed 行共用 100 行环,100 条失败会把 billed 行挤光、把摘要清零。现在 incoming billed 先逐出最旧的 failed(全是 billed 才逐出最旧 billed);incoming failed 只逐出 failed,全满时丢弃自己。失败永远不能挤掉计费行;
summary仍只统计 billed。 - 测试:P2-4/P2-6 红先行 2+1+3 条失败,修复后
account-entitlement18/18、userstatus-teams-tier-log7/7、dashboard-account-requests15/15;突变auth-truth-p2.json三层各被捕获(手工在 Windows 上应用/还原并核对 sha256)。
4. 上游 4xx 校验拒绝是 400,不是 503
AccountMatrix §7.1 的 106 字节尾帧是 {"error":{"code":"invalid_argument","message":"an internal error occurred"}}(图片发给 /v1/models 省略 supports_images 的模型)。旧的文本 arm 先匹配 internal error occurred,于是显式的 4xx 校验码被读成瞬时后端故障:503 UPSTREAM_INTERNAL、可重试、并给健康账号记 internal-error streak。
- 新增
REQUEST_REJECTED裁决:显式invalid_argument/failed_precondition不再被文本 arm 抢走;保留上游消息并追加能力提示(点名supports_images与消息/工具形状)。配额、容量、内容策略、selector、tier、auth、限流等更窄裁决仍然优先。 - HTTP:
REQUEST_REJECTED → 400 invalid_request_error(流式同一类型);不计罚、不冷却、不 re-login,计request_rejected;isRetryable永不重放,即使包在 5xx 壳里。没有 4xx code 的真瞬时 5xx 仍是 503。 - 探针
evidence/UpstreamErrorMapping-probe.mjs重放真实 106 字节穿过完整链:前503 UPSTREAM_INTERNAL→ 后400 REQUEST_REJECTED,upstream_hits: 1(未重试);容量/内部瞬时控制不变。test/connect-request-rejection.test.js16/16(红先行 9 失败),9 条新突变全部被捕获。
5. support bundle:打包证据,不打包凭据(P1 / P2-7)
scripts/support-bundle.mjs 把最新 trace legs、/health 提交与版本、脱敏后的开关快照打成一个 support-bundle-<utc>.zip 加逐文件 sha256 manifest。wave-6 的首版把二进制 leg 原样复制,于是 03-upstream-req.bin 里的 session token / user JWT 进了压缩包;文本 leg 也漏掉账号邮箱与 presigned URL 凭据。
- 二进制请求 leg 现在是解析后脱敏:
GetChatMessageRequest#1 ClientMetadata 的 #3(session token)与 #21(user JWT)值替换为[redacted],其余字节原样保留;manifest 记sourceBytes/sourceSha256与transforms。无法识别为请求的二进制 leg 省略并记录原因与 sha256(响应的 gRPC 帧无法证明内容,只能省略)。 - 文本 leg 增加:凭据字段值、
X-Amz-*与Signature/token/key/api_key/access_token/refresh_token/session_token/password/secret查询参数、邮箱形状。独立 backstopfindResidualSensitive()要求这些名字后面的值必须是[redacted];所有被移除的凭据值还会在写 ZIP 前对整棵 staging 树逐字节搜索,命中则 exit 1 且不写任何文件。 - 退出码不变:0 = 写出;1 = 门禁命中(共享规则或残留凭据);2 = 用法/IO/环境失败。测试
test/support-bundle.test.js12/12(旧代码 4 fail + 5 cancelled);独立用 Pythonzipfile复检三个真实 builder trace:JWT、opaque token、邮箱+presigned URL 均不在任何 entry 中,manifest 的binaryLegs.redacted/omitted与存储字节数可对账。8 个手工突变(#3/#21 原样、关闭 staging 搜索、去掉邮箱脱敏、去掉查询参数脱敏、原样 stage、只按isText分类、不脱敏 environment、只脱敏 #3)每个都有测试变红。
6. 诊断与文档基础设施
- 失败尝试历史:
recordAccountRequest给每行盖billed;新的recordAccountAttempt写零成本billed:false行(outcome+status),没有账号 id 就不写行。CONNECT 的 429/402/连接错误/abort 与 Cascade 的 LS/abort 退出都会归到被尝试的账号;GET /accounts/:id/requests返回billed/failed,drawer 分行显示,Devin 面板的 billed turns 过滤失败行。计费摘要逐字节不变(失败行不移动 requests/tokens/credit/acu/lastTs)。 - 未验证登记册:
scripts/unverified-register.mjs从 24 份 release notes 的「尚未验证」小节生成docs/UNVERIFIED.md(file:line、文本、marker、状态),test/unverified-register.test.js逐字节比较并验证幂等与 marker 映射。本版新增的缺口也进入登记册。 - §0 src-lag 守卫:只检查祖先不够——§0 曾命名一个落后 10 个提交、6 个
src/文件的 master 而守卫全绿。docs-consistency-guard现在跑git diff --name-only <§0 master>..HEAD -- src/:docs-only 滞后允许,src/变化必须已被 §0 指向。自测钉住predates N src/ change(s)文案与 HEAD 自身不误报。
门禁
(本表每一行都写出来源;[UNVERIFIED] 表示至今没有任何测量。)
| 项 | 命令 | 结果 | 来源 |
|---|---|---|---|
| spec 静态一致性 | node scripts/spec-static-check.mjs |
PASS — 96 份 spec / 855 条突变,anchor 各命中一次 | 本版运行 |
| Windows 本地门禁 | GATE_INERT_SKIP_PATHS="test/wire-byte-identity.test.js;test/devin-connect-relogin-live.test.js;test/mutate-verify-harness.test.js;test/self-update-untracked-safety.test.js;test/detach-soak.test.js;test/detach-ls-pool-pressure.test.js" npm run gate |
PASS — 6,774 pass / 0 fail / 96 skip(560/560 文件实测;5 个 inert 文件,6 条声明;test/wire-byte-identity.test.js 因本机基线存在而实际执行并通过,被报 unused) |
本版运行 |
| wire(本地,对上一发布) | WIRE_BASE_TREE=.claude/worktrees/wire-base WIRE_BASE_REQUIRED=1 WIRE_BASE_TAG=v3.141.592654 WIRE_BASE_SHA=d5e7ace… node --import ./test/setup-env.mjs --test test/wire-byte-identity.test.js |
PASS — 258 byte-identical + 12 declared = 270/270;WIRE_PROVENANCE_COMPARED {"compared":270,"expected":270,"base_head":"d5e7ace…"},WIRE_EXPECTED_DIFFS {"byte_identical":258,"expected":12,"entries":12,"compared":270} |
本版运行(Windows,node v24.19.0) |
| wire(因果对照) | 同一矩阵,只把 src/handlers/tool-emulation.js 回退到 4406ca6^ |
270/270 全等 —— 12 个差异恰好是 #273 重排 | FinalMatrix @ d69e767(HMC) |
| wire(v3.141.592654 对 v3.10.1) | release gate 形态 | 270/270,tag_ref_verified=true |
FinalMatrix @ d5e7ace(HMC) |
| docs 一致性 | node --import ./test/setup-env.mjs --test test/docs-consistency-guard.test.js |
PASS — 47/47 | 本版运行 |
| 未验证登记册 | node --import ./test/setup-env.mjs --test test/unverified-register.test.js |
PASS — 生成器逐字节一致 | 本版运行 |
| Linux(HMC)Node 24.21.0 | node scripts/run-tests.mjs(559 文件) |
6,858 tests / 6,853 pass / 0 fail / 5 skip | FinalMatrix @ d69e767 |
| Linux(HMC)Node 22.23.4 | 同上 | 6,858 tests / 6,853 pass / 0 fail / 5 skip | FinalMatrix @ d69e767 |
| 全量 mutation sweep(96 份 spec) | node scripts/mutation-sweep.mjs |
PASS — 96/96 spec;855 executed = 841 caught + 14 条声明的 expectCaught:false,0 意外幸存、0 drift、0 untrustworthy;VERDICT coverage: 96/96; exit=0 |
FinalMatrix @ d69e767(HMC,node v24.21.0) |
npm run gate 的摘要逐字如下:
PASS spec-static-check exit=0
PASS spec-baseline-check exit=0
PASS secret-scan exit=0
PASS test:release exit=0 — 6774 pass / 0 fail / 96 skip (560 files) — 5 inert file(s), 6 declared (unused: test/wire-byte-identity.test.js)
PASS git diff --check exit=0
PASS git diff --cached --check exit=0
SKIP mutation EXECUTION — this gate checks spec shape, anchors and baseline values, but does not run the mutants themselves; the Linux full-gate recipe runs the sweep.
INCREMENTAL GATE: PASS exit=0
TEST_SHARD_SUMMARY {"files":560,"measuredFiles":560,"tests":6870,"pass":6774,"fail":0,"skipped":96,"cancelled":0,"todo":0}
已知 / 尚未验证
全部历史缺口见 尚未验证登记册;本版新增的条目也在其中。
- wire 已实测,但只覆盖请求体:本版本地对 v3.141.592654(
d5e7ace,annotated tag 已验证)258 个逐字节相同 + 12 个声明差异 = 270/270 对账;同一矩阵在 master 上对同一基线得到相同的 12 个差异,且只回退tool-emulation.js后 270/270,证明差异因果上就是 #273。比较范围仍是 9 组历史 × 5 条 prepare 路由 × 2 native × 3 环境 = 270 个 protobuf 请求体;HTTP/Connect 帧与响应流不在内。 - expected-diff 允许列表是自失效的:比较基线一旦包含 #273,12 条必须删除,否则 wire 门报 stale。它是逐比较的,不是全局放行。
- 全量 mutation sweep:96 份 spec / 855 条突变,结果见门禁表;Windows 本机仍不能跑 mutation harness(需要受信 POSIX git)。
- 真实客户端未重跑:wave-7 的图片、顺序、账号真相、计费历史修复由单元测试、真实 handler 与捕获帧探针证明;没有用真实 Claude Code / Codex / Devin 客户端复验这些路径。
- #273.2 未校准:多帧 reasoning 重复只复现了 fixture 行为(raw 3 份 / 预处理后 2 份,与 issue 一致),缺真实 Devin 客户端抓包;编码器未改。
- #149 是政策决策:静态基表保持一个模型,依据是历史测量与 #155 的 probe 说明;没有在本版重新对免费账号做 live entitlement 探测。
- support bundle 的边界:凭据值在 staging 树中逐字节证明已移除;二进制请求里的 prompt/system 文本不扫描邮箱(它是用户内容,且已在 README 声明会打包);prompt 文本里粘贴的、与请求元数据无关的 opaque 凭据无法被识别。未用真实付费账号的凭据 trace 复验。
- 本机门禁的 inert 声明:Windows 没有
/usr/bin/git,test/devin-connect-relogin-live.test.js未武装,test/mutate-verify-harness.test.js/test/self-update-untracked-safety.test.js因REAL_GIT为 null 不执行,detach-soak/detach-ls-pool-pressure在本机整文件跳过——这 5 个按GATE_INERT_SKIP_PATHS声明,不当作绿。test/wire-byte-identity.test.js也声明了,但本次运行存在本机基线(.claude/worktrees/wire-base@d5e7ace),它实际执行并通过,所以被报 unused;没有基线的主机仍会整文件跳过。 - 托管 CI / 发布产物未验证:本版在本地分支准备;tag、release workflow、HMC 部署与 cosign 资产都要在合并后另行执行与核对。