Repository navigation
[BUG] 平台拒绝有两种信封形状,咽喉只认扁平那种 —— gateway 模型面的稳定码被结构性丢弃 #962
Description
这是什么
#918 / #940 花了两轮把「从平台响应里取稳定分类码」收编成咽喉(platform-error-code.ts),
为的是让用户看到「你要的那条限制,这个执行形态根本强制不了」这类诚实说明,而不是一个 http-400。
但平台的拒绝有两种信封形状,咽喉只认其中一种。
主 session 实读确认(不是转述):
咽喉只读顶层 code —— packages/ui-mac/src/main/platform-error-code.ts:
const body: unknown = JSON.parse(await res.text())
const code = (body as { code?: unknown } | null)?.code
return typeof code === "string" && CLASSIFICATION_CODE.test(code) ? code : fallback而 gateway 的模型面发的是嵌套形状 —— alpha-platform/packages/gateway/src/worker.ts:
:178 { error: { message: "refusing to serve: PLATFORM_ENV=dev …", code: "dev_semantics_on_production_target" } } 500
:268 { error: { message, code: BILLING_UNREADY_CODE, missing: r.missing } } 503
:281 { error: { message: "rate limited: too many requests from this IP", code: "rate_limited" } } 429
:527 { error: { message, code: "settle_intent_unavailable" } } 503
:894 { error: { message: "forbidden: token is not authorized for model.invoke", code: "action_forbidden" } } 403
⇒ 这些码结构上到不了咽喉:body.code 是 undefined,直接回落 http-429 / http-500 / http-403。
为什么这比「少一个码」更糟
咽喉的注释与 #940 的判据都写着「无 code ⇒ 保持 http-<status>,不猜」——那是一条
fail-closed 的诚实纪律。但在这条路上,码是有的,我们只是没读。
于是下一个人读到 http-429 会以为「平台这次没给码」,而事实是给了,被丢了。
一条 fail-closed 的纪律,被用在一个不该 fail-closed 的地方,就变成了误导。
⚠️ 可达性未实跑 —— 这是修之前必须先补的那一半
形状差异是逐行读出来的、确凿;但「用户真的会经 /v1/models 撞上嵌套码」这一步,
勘破是按中间件注册模式推出来的(worker.ts:291 的 /v1/* rateLimitGate + :185 的 *
envIdentityServeGate),没有实跑——现有测试用 /v1/chat/completions 驱动那只桶。
按本仓纪律「勘破先于闸门设计」「散文断言不算勘破」:先补一条实跑判据证明这条路走得到,
再谈修。如果实测发现桌面端根本不经那条路撞上嵌套码,那这张票就该收窄或关掉。
现成的实现参考(但别整套搬)
已作废关闭的 PR #956(分支 fix/940-platform-error-code-chokepoint 保留未删)里,
那套平行咽喉 platform-http-error.ts 本来就同时认两种形状
(cloud 顶层 {error,code} 与 models/account 嵌套 {error:{message,code}})。
可以取它的解析形状作参考,但不要把那整套平行咽喉搬回来 —— 现行咽喉是 #955 的
platform-error-code.ts,两套并存是我们刚消灭掉的形态。
边界
packages/ui-mac/src/main/platform-error-code.ts 的解析。
不要改平台侧的信封形状 —— 那是 ap#330 的范畴,而且那张票的勘破已经证明「统一形状/补码」
这个方向的体量约 290 处、且会打破一条用户可达路径。本票只做消费端认两种形状。
退出条件
- 先勘破:补一条实跑判据,证明桌面端确实会经某条路径拿到嵌套 code(
/v1/models或别的)。
拿不到就说清楚拿不到,收窄票面。散文断言与「按注册模式推理」都不算。 - 咽喉同时认两种形状;**
fail-closed 那一半不能松**:两种形状都没有合法 code 时,仍然保持http-`,不猜。 - 判据落在用户可观察结果上(错误行里出现的是那个码,不是
http-429),不要只断言解析函数的返回值。 - 新增/改动的判据要能被跑到:注意本票涉及的两个测试文件此前不在
gate-files.tsv里
(#940合并时已登记alpha-upload.test.ts与upload-consent-dialog.test.ts)。 - 写完自己实施一遍绕过:把嵌套解析去掉,判据必须转红。
复杂度:S~M。来源:ap#330 只读勘破(2026-08-13)+ 主 session 复核。
Refs #918、#940、#955、jinjunnn/alpha-platform#330。
Activity
jinjunnn commentedon Aug 14, 2026
编排者裁决(2026-08-14):票面收窄 + 排在
#965之后,不在本批实现本轮对 Ready 池做了 10 条 lane 的只读勘破,本票的那条把「票面自己点名未做的那一半」补上了。结论有三条,其中两条改变票面。
1. 形状差异属实,而且是实跑出来的
同一条拒绝(IP 限流 429),两个 worker 两种信封,同一个咽喉一个认一个不认:
码是有的,确实被丢了。这一半不用再查。
2.⚠️ 但退出条件在这条路径上结构性写不出来 —— 票面要改
票面写「判据落在用户可观察结果上(错误行里出现的是那个码)」。那条错误行今天不存在:桌面对 gateway 唯一经咽喉的请求是
alpha-platform-models.ts:46的GET /v1/models,它的错误串三个消费点全部断头 ——main/index.ts:735与:1317两处.catch(() => {})丢弃返回值,IPCmodels-platform-live自97f790706(#442/#447 删 hidden-picker)起零 renderer 调用方。三条独立轴查证。所以本票不能按原口径验收。可选项:(a) 收窄成解析层 + 如实写明本路径今天无用户出口;(b) 重心挪到 account 面(那里有用户出口);(c) not-planned。裁决:走 (a)+(b) 的组合,但要排序 —— 见下。
3. 真正值钱的发现不在票面上:先修本票会制造一个用户可见的退步
#955把alpha-account-request.ts接进咽喉时,丢掉了已作废分支里那条明写的豁免。实跑accountResultState:http-503→recovering(正在恢复,继续重试)billing_unready→failed(终局,停止重试)account-server 的真实信封正是嵌套的
{error:{message,code}}。⇒ 一旦咽喉认了嵌套形状,一个瞬态的 billing-unready 会被判成终局,composer 停止重试 —— 而 account 面是有用户的那条路径。裁决
#965之后,不在本批实现。#965(model-recovery 的 transient 判定改读结构化status,删掉从错误字符串抠数字的正则)是本票的安全前置:它一落地,上面第 3 条那颗雷就结构性消失 —— transient 判定不再看字符串形状,咽喉认不认嵌套码就与「要不要继续重试」解耦了。#965已进 2026-W33,本批在做。#965合并后,本票按 (a) 收窄执行:咽喉同时认两种形状,fail-closed 那一半不松(两种形状都没有合法 code 时仍保持http-<status>,不猜),并在票面如实写明/v1/models这条路径今天没有用户出口、判据只能落在解析层 + account 面。platform-error-code.ts/alpha-account-request.ts/model-recovery.ts),同期开工只会互相冲突。顺带订正票面三处(勘破实读)
billing_unready结构上到不了/v1/models(readinessGate的路径枚举不含它);真正可达的只有 2 个。gate-files.tsv那段两个方向都反了:票面说「已登记」的alpha-upload.test.ts/upload-consent-dialog.test.ts其实不在册;而本票真要改的platform-error-code.test.ts/platform-error-code-gate.test.ts在册且是精确条数 4/4 —— 加一条用例就红,实现方必须同批更新登记条数。勘破全文留在本轮 session 的证据目录;以上每一条都有真实命令与输出,没有散文断言。
jinjunnn commentedon Aug 15, 2026
订正块(2026-08-14,实现方)—— 原文一律保留,由本块点名覆盖
按本仓「基线/票面被推翻时原文划掉保留 + 带日期订正块」的规矩。下面每一条都对应上面某一句,不是重写。
一、退出条件「判据落在用户可观察结果上」——在全部五个消费者上今天都写不出来
account 面也没有出口。 三条独立轴:
account-server.ts的 35 个嵌套信封里带code的是 0(唯一带码的/v1/preauth是 gateway→account 的服务器间端点,桌面到不了);api.account.summary调用点(alpha-composer.tsx/alpha-composer-model.tsx/renderer/sidebar/alpha-sidebar.tsx)全部只读三态,从不渲染error字符串(出错时渲染的是—);account.transactionsIPC 零 renderer 调用方。三个 cloud 消费者的宿主
cloud.ts嵌套信封数 = 0(全扁平),本来就工作;唯一真用户出口cloud-dispatch-box.tsx的dispatchError(code)走的正是那条扁平面。⇒ 本改动今天带来的用户可观察行为变化是 0 条,在全部五个消费者上。 已按此交付并在 PR 里写明。
它值得做的理由与
#965同类:这条依赖会一直生息 —— 平台每加一处 gateway 面的分类码,今天都白加。降级如实标注(不假装有闸): 退出条件①(实跑证明桌面会经某条路径拿到嵌套 code)由一次性跨仓探针满足
(勘破轮导入真 gateway worker,
GET /v1/models限流 ⇒ 真 429 嵌套信封 ⇒ 喂进真咽喉得http-429)。本 PR 未留下持续判据 —— C1~C5 全是本仓内的合成夹具,平台哪天改掉
/v1/models面的信封形状,本仓不会红。二、
gate-files.tsv那段:只反了一个方向,不是两个实读 HEAD
4558a09e3的scripts/gate-files.tsv:第 191 行登记alpha-upload.test.ts(精确 8)、第 192 行登记
upload-consent-dialog.test.ts(精确 13)——两个都在册,票面括号里那句本来就是对的。只有票面另一半是错的:本票要改的
platform-error-code.test.ts(:188)与platform-error-code-gate.test.ts(:189)在册且是精确条数 4/4,
#844起第 1 列是精确条数不是下界 ⇒ 加一条用例就红,必须同批更新登记数。本 PR 已把 :188 改成 6,:189 保持 4(该文件未加用例)。
为什么这条必须订正:照「两个方向都反了」去补登记 = 往登记簿加重复行,而
assert-gate-files.sh对重复(workdir,path)判红 —— PR 会在一个与本票毫无关系的地方红,而最省事的止红方式是把行改成>=N例外,一道精确条数闸就此退回下界。
三、常量名与行号
行号今天是
:180/:269+:270/:283/:528+:529/:907(票面写的是 178/268/281/527/894,漂 2~13 行)。BILLING_UNREADY_CODE的值是billing_dependencies_unready(readiness.ts),不是票面写的billing_unready;现有
platform-error-code.test.ts里的billing_unready是合成字面量,不对应任何真实平台码(作为独立锚点合法)。五个嵌套点里
/v1/models上可达的只有 2 个:rate_limited(429)与dev_semantics_on_production_target(500)。billing_dependencies_unready结构上到不了 ——readinessGate的注册列表不含/v1/models(实跑证实:prod 依赖全缺时/v1/models仍 200)。四、「跨仓契约冲突」——查证后不成立,已按 (a) 开工,未改平台侧
勘破轮曾提出「让咽喉认嵌套 = 让 alpha-platform
cloud-jobs-v1.md当天写下的顶层槽契约变假」,并据此建议改平台侧。对抗审计把它推翻了,复核成立:那句话作用域是 cloud schedules 面、主语是
details.code;gateway 面归wire-contracts-v1.md(全文无顶层码规则);且/v1/messages上嵌套形状是 Anthropic wire 的结构性必需。⇒ 消费端多认一种形状不会让那两句里任何一句变假。本 PR 未触碰 alpha-platform。
平台侧的对齐建议(两个中间件把码放顶层)与一句文档同步,已另开
jinjunnn/alpha-platform#367,不阻塞本票。/v1/models死错误链路另开#987(P3 观察票)。docs(#962): C3 的立论改成「防等价实现分叉的锚」,不假装它拦着一条真实路径
fix(#962): 咽喉同时认扁平与嵌套两种信封形状 —— gateway 面的稳定分类码不再被丢 (#988)