Fix/修复登录用户参数等错误被上报 #567
Reference in New Issue
Block a user
Delete Branch "fix/wrong-report"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
close #562
1. clientAuthError.ts 形状读取不校验
status(原 [maintainability · low])isClientAuthError只校验object+ 非空string type+string message,然后直接 cast 成ClientAuthError;而生成类型里authServiceUnavailable/unexpectedRejection带必填status: number。{ type: 'authServiceUnavailable', message: '…' }这类缺status的载荷会通过校验,下游按类型读
status会拿到undefined。当前没有任何调用方读status(这两个变体只走fault分支显示
message),所以是契约一致性问题,不是线上故障。90d37481a在 docstring 里把"故意不校验"写成明确约定并给出理由:把status纳入校验,只会让"缺
status但message可用"的拒绝落回非结构化分支,Rust 文案被换成调用方兜底文案,行为更差;将来真要用
status判断时先补校验再用。不改判定逻辑,避免引入上面的回归。2. refresh 失败被硬编码成
network,丢掉error_kind()(原 [bug · medium])ClientAuthError::error_kind()会把失败分成 network / service / response / storage / runtime / auth;ClientAuthRefreshView只带error_message,不带 kind。于是read_client_auth_state在refresh.status == "failed"分支(auth_session.rs:1048)写死error_kind: Some("network");只有
refresh_session_inner直接Err(...)的那条分支(:1056)才用error.error_kind()。AuthServiceUnavailable)或响应不合法(AuthResponseMalformed)会被标成network,分类字段与事实不符。影响面目前有限:TS 侧
RustAuthStateView.errorKind没有消费方(
readClientAuthState只看 status/user/apiBaseUrl,subscribeClientAuthState同样不看),所以今天没有用户可见的错误提示走错分支;但一旦有人拿
errorKind做重试/文案/埋点,就会踩坑。ClientAuthRefreshView末尾加error_kind: Option<String>(带#[serde(default, skip_serializing_if = "Option::is_none")],与
ClientAuthStateView同款),failed分支填Some(error.error_kind().to_string());read_client_auth_state的failed分支改用refresh.error_kind而不是写死的"network";refreshClientAuthSession的本地类型加上errorKind(现在failed只用errorMessage,可按需透出),并同步 auth 相关文档/ADR 的投影字段说明;
error_kind == "service"(或直接测map_auth_failure的分类到
error_kind()的映射),避免以后又退回写死值。注意:
ClientAuthRefreshView是命令返回的 wire 结构,虽然 TS 侧是手写类型不是 ts-rs 生成,加字段仍属契约变更,按仓库约定要同批更新文档。
3. 空文案的业务变体被误判成需要上报(原 [bug · low])
resolveClientAuthFailure之前是notice = clientAuthErrorNotice(structured),notice !== null就算 notice 分支。
clientAuthErrorNotice对fault和"文案为空"都返回null,两种情况被混在一起:可识别的
input/session变体只要message为空/全空白,就会掉进kind: 'report'进入错误池,违背"预期业务拒绝永不进池"的约束。
8b50db989改成用clientAuthErrorKind(structured) !== 'fault'判分流,提示文案用clientAuthErrorNotice(...) ?? getClientAuthErrorMessage(error, fallback);并在clientAuthErrorNotice的 docstring 注明null的双重含义、明示它不是分流判据;clientAuthHost.test.ts增加"空文案业务变体 → notice + 兜底文案"的用例。4.
map_auth_failure的prefixed闭包会触发 E0505 借用错误(原 [bug · critical])let prefixed = || format!("{fallback}:{}", server_message.clone().unwrap_or_else(...))以共享借用捕获
server_message;下面UNAUTHORIZED/FORBIDDEN分支里server_message.unwrap_or_else(...)会移动它。return match route {...},移动只发生在直接返回的路径上;NLL 判定这些路径上闭包借用已死,因此不报 E0505。
touch server-rs/crates/shared-contracts/src/lib.rs && cargo check --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml通过(只有既有 warning),无需改动。let prefixed = { let server_message = server_message.clone(); move || ... },或每个分支自带server_message.clone()。现在不必动,避免为假阳性引入 clone。1. direct_turn_error.rs 模块文档重新引入退役词「拒单」(原 [documentation · low])
DirectTurnEnqueueFailure」。【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24§1 明确「接单」「拒单」退役,不再出现在文档、注释、标识符与测试名里;这个新模块文档把词又带回了 DirectProject 域。
同一验收下
tests/agentRuntimeModel.test.ts还有 3 处(用例名 + 注释)也在用「拒单」。b202c7027把模块文档与那 3 处统一改成「入队失败」。剩余两处不是违规:direct_runtime/mod.rs:4344引用的是 2026-09-23 ADR 的文件名(改不了文件名),codex_app_server/mod.rs:39的「每连接单行」是子串误报。2. 文档把
DirectTurnEnqueueFailure.message说成只来自Display(原 [documentation · low])Display文案」。message有两条来源。direct_turn_enqueue_failure()(direct_runtime/mod.rs:2047)里,is_reportable()为 false 的失败走DirectTurnEnqueueFailure::new→error.to_string()(Display);为 true 的宿主 / 环境事实走
record_direct_codex_failure→direct-codex-failure:v2 stage=… code=… retryable=… summary=…;建议:…,同时写.agent/runtime/errors与错误上报池。原文只说
Display,与事实不符。d1e637b9f把两条来源写进模块文档,并保留「Rust 侧任何地方都不解析这个字符串」的约定。3. AuthenticatedClient 两处失败处理重复(原 [maintainability · low])
handleSendCode与handleLoginSubmit的 catch 里各写了一份「resolve → setLoginStatus →kind === 'report' 时 captureClientError(new ClientActionError(...))」,只有 fallback 文案与
action不同。action,两份实现将来容易漂移(改一份忘一份)。698b3ffc9抽出组件内的presentAuthFailure(error, fallback, action),两处 catch 各一行;行为不变(appSurface 194 passed)。
4. 网络失败文案仍带
network-error:机器前缀(原 [maintainability · low])AUTH_NETWORK_ERROR/AUTH_NETWORK_TIMEOUT以network-error:开头,经AuthNetworkUnavailable { message }→ 前端resolveClientAuthFailure原样显示。type分流,没人再解析该前缀(AGCsrc/无消费方,Rust 侧也没有starts_with/contains("network-error")),用户却能看到「network-error: 无法连接登录服务…」。同文件的
AUTH_AUTHORITY_MESSAGE早已去掉authentication-required:前缀,这里没跟上。c5153a7ed去掉两条常量的前缀;auth.suite.ts、clientAuthHost.test.ts的对应fixture 与断言同步更新。
5. 续期凭据缺失文案仍带
result-unknown:前缀(原 [maintainability · low])CookiePolicy::Require且没拿到新 cookie 时返回AuthResponseMalformed { message: "result-unknown: 登录服务未返回新的续期凭据…" }。authResponseMalformed,该前缀没有解析方,只会出现在用户可见文案里。注意:
resource_editor/platform_asset_upload等链路的result-unknown:与这里无关,有各自的断言与用途,不动。
402233ae9去掉该条消息的前缀,文案变成「登录服务未返回新的续期凭据,已停止使用旧凭据」。6. refresh-failed 分支把
error_kind写死成network(原 [bug · low])— 条目已过期read_client_auth_state的Ok(refresh)"failed"分支写死error_kind: Some("network"),而Err分支用error.error_kind()。5d5aafe25整体删除——ClientAuthStateView.error_kind全仓 0 个读取方(TS 也从不读),所以不存在"传下来还是写死"的取舍,条目自然作废。现在
"failed"分支只投影error_message,Err分支同样只投影error_message,两侧一致。- ADR §1 增加"每个变体一个具名载荷结构体 + ts-rs 生成 `{ type } & X`"的决策与代价 - ADR §3 说明 switch 分支里错误已窄化成具名类型 - 技术方案同步命令错误形状描述 - decision-log / pitfalls 记录形状约定:不手写这层类型、不再包派生分类与提示文案函数- auth_error.rs:枚举改为 newtype 变体,每个变体持有一个同名 `#[ts(export)]` 载荷结构体 - auth_session.rs:41 处构造点改为 `ClientAuthError::X(X { .. })` 并导入载荷类型 - 重新生成 services/generated:ClientAuthError.ts 变成 `{ type } & 具名类型`,新增 19 个载荷类型文件 - tests/clientAuthError.test.ts:新增编译期用例,switch (error.type) 分支里错误窄化成具名载荷类型- 更新 AGC 命令错误结构化 ADR:变体拍平不嵌套,无字段变体生成 { type },有字段变体生成 { type } & 载荷类型 - 记录本地前置校验拿不到细分事实时不编字段,phoneNumberInvalid 保持无字段 - 更新 AGC 认证失败 JS 侧载体 ADR:无字段分支用固定文案,带载荷分支先 as 再读 serverMessage/status/detail - 更新错误报告技术方案与 README 索引,同步拍平变体与去 reason 口径 - 同步决策记录与踩坑:ts-rs 只写不删,变体改成无字段时要手动清孤立载荷文件- auth_error.rs:变体拍平到同一层,无字段变体序列化为 { type },载荷变体只装 serverMessage/status/detail - 载荷结构改名并去掉预拼文案:PasswordLoginRejected/SendCodeRejected/PhoneCodeLoginRejected 持 Option serverMessage - auth_session.rs:服务地址校验 7 种失败各成一个变体,网络拆成超时/不可达,响应契约拆成 5 种变体 - map_auth_failure/response_data/request_auth 去掉 fallback 文案参数,只按可判定事实映射 - 定向用例改用新变体名,并对原始载荷做断言- 新增有字段变体的载荷文件 PasswordLoginRejected/SendCodeRejected/PhoneCodeLoginRejected/AuthResponseServerRejected - 删除改成无字段变体后不再生成的孤立载荷文件 - ClientAuthError 联合同步为 { type } 与 { type } & 载荷类型- ClientAuthError 联合收敛为类别变体 + 类型化 reason,无字段变体仍是 { type } - 新增 AuthNetworkFailure/AuthNetworkReason/AuthResponseInvalid/AuthResponseInvalidReason/ServerAddressRejected/ServerAddressReason - 删除拍平变体后不再生成的 AuthResponseServerRejected 载荷文件- 更新 JS 侧载体 ADR:ClientAuthErrorWrapper 构造时把整份载荷 JSON.stringify 进 Error.message,上报事件拿到变体名与载荷,事件指纹按变体区分 - 记录 Tauri 缺陷抛真 Error 时序列化只有 {},但上报链路对真 Error 优先用其自身 message/stack - 同步决策记录与技术方案的载体口径,不再写 Error.message 留空- 载体用例断言 message 为载荷序列化;Tauri 缺陷抛真 Error 时是 {},但上报仍优先用其自身 message/stack - 上报用例的 message 断言改为序列化结果,并新增真 Error 优先分支用例1. 已修(5f76e3bb5):AGC 认证 catch 的 default 分支补可见提示
hydrateAuth/sendCode/login三个 catch 的default:在 rethrow 前先写一行泛化文案「登录服务返回了未识别的错误,请重试或提交错误报告」,结构化映射外的 Tauri/IPC 失败不再停在空白状态;仍然原样抛出走上报。authFailureReporting.test.tsx补一条钉住可见提示。3. 已修(0803e035b):errorReporting.ts 取文案的嵌套三元改 if/else 赋值
let errorValue: Error+ if/else 链,行为不变;errorReporting 12 个用例通过。5. 已修(325ebda7e):auth.suite.ts 启动超时用例的重试按钮断言退化成存在性
toHaveProperty('disabled', false),恢复「可点重试」回归覆盖;appSurface 194 passed。6. 已修(7528ba011):unhandledRejectionBridge 用
rawListeners恢复监听器.listener判断走process.once;未被消费时直调包装内原函数。顺序问题保留(安装期间新增的监听器仍排在前面),已写进注释。7. 已修(7278023cb):auth.suite.ts 业务变体用例用
return Promise.reject(...)混风格throw {…}+return { status: 'unauthenticated' }。8. 已修(b54b3794f,文档 f8c32e634):登录路由 429 落到 UnexpectedRejection 的系统变体口径 bug
AuthRoute::PhoneLogin→PhoneCodeLoginRejected { server_message }(复用现有业务变体、不新增),其余路由仍UnexpectedRejection;补 3 条 auth_session 用例。9. 已修(订正版:文档
d518bb2ce+ 代码 7128e865c):本机 IO 失败的detailclientSessionPersistFailed/runtimeSessionInstallFailed是带detail的载荷变体,detail装原始 error,供调用方分流与报告诊断。本机登录凭据读写失败/本机运行时会话安装失败,带各自前缀)。ClientAuthErrorWrapper序列化进Error.message入池;路径等由报告侧 sanitize 换成<path>,不靠 UI 脱敏。app_log!(落盘前过sanitize_diagnostic_message)记一行。10. 已修(da4645f55):auth.suite.ts 桥接安装 / reason 收集 / 拆卸重复
captureWindowRejections()(返回{ reasons, uninstall }),顺带修正系统变体用例里过期注释。11. 已修(e1e8725da,文档 d518bb2ce):AuthClientInitFailed 丢掉 reqwest 原始错误
authClientInitFailed从 unit 变体改成带detail的载荷变体,build_client()失败时把 reqwest 原始错误放进detail;导出AuthClientInitFailed绑定;carrier 用例按带载荷变体as取detail。创建登录网络客户端失败),不展示 detail。review 处理结果(2026-10-02 第三轮)
8 条:自动修 3 条(1/2/3),你拍板修 4 条(4/7/8 + 7 按你定的 string 设计收尾)。剩 2 条(5/6)留给你。
1. 生成绑定的 JSDoc 抄了 Rust 语法(已修 74e7209c3)
auth_error.rs里Option<String>字段的文档写`None`,AuthResponseInvalid.server_message写`AuthResponseInvalidReason::ServerRejected`;ts-rs 原样抄进生成的.tsJSDoc。`null`/`serverRejected`并重生成;顺手修了同一类的另外 3 处(PasswordLoginRejected/SendCodeRejected/PhoneCodeLoginRejected)。2. auth.suite 启动超时用例断言太松(已修 3d4c42901)
toHaveLength(1)再断言这一条是含「检查登录状态超时」的Error;无关拒绝不再被静默吞掉。3. 拒绝桥交回原监听器时
this为空(已修 b0f9eb229)Reflect.apply(listener.listener ?? listener, process, [reason, promise]),补一条「this === process」用例。4. 合成事件不是
PromiseRejectionEvent(已修 27f7375b5)unhandledrejection用的是PromiseRejectionEvent(Event子类),标准字段reason+promise。new Event('unhandledrejection')+ 只挂reason,event.promise永远是undefined;jsdom 22 不实现PromiseRejectionEvent。PromiseRejectionEvent构造器就直接用,没有(jsdom)退回普通Event;两者都把reason/promise覆盖成实例访问器 →promise可读了。读reason或promise任一个都算 window 侧接手(原来只认reason)。补了一条「事件带 promise 且读 promise 也算被消费」的用例。5.
subscribeClientAuthState去掉了 payload 空值守卫view.status === 'authenticated'直接解引用;view?.status === 'authenticated' && view.user && view.apiBaseUrl这个守卫在ce54c729e里被删。subscribeTauriEvent把 payload 直接as RustAuthStateView,坏载荷会在事件回调里抛 TypeError,而 trailing.catch(() => () => {})只盖订阅建立、盖不到回调。unhandledrejection进错误池。恢复守卫会把坏载荷静默降级成「未登录」,更难发现。补充:这个RustAuthStateView是手写类型(不是 ts-rs 生成),信任边界比错误枚举弱。6. 三处 catch 的
switch重复 ~150 行hydrateAuth/handleSendCode/handleLoginSubmit各有一份完整的 failure→文案switch。7.
/api/auth/phone/login的 401 被标成SmsCodeRejected(已修 836f8a734,保持 string 设计)PhoneAuthError::UserNotFound(phone_auth.rs:410);验证码错误/失效/过期都是 400,已由PhoneCodeLoginRejected { serverMessage }带原文。PhoneCodeLoginRejected { serverMessage };退役顶层变体SmsCodeRejected(唯一产生点就是那个错配分支);前端三个 catch 删掉该分支,文案统一「验证码登录失败:<服务端原文>」(冒号改全角);docs + 绑定 + 用例同步。8. 读 body 失败一律归
authNetworkFailure { unreachable }(已修 a51278699)status后response.text()失败 → 一律AuthNetworkFailure { Unreachable },已知状态码被丢掉。classify_unreadable_body(status, route)——非 2xx 按状态码走既有分类(serverMessage为None,如 503 →authServiceUnavailable { 503 }、会话 401 →sessionInvalidated),只有 2xx 响应没收完才算传输层故障;补单测覆盖 503 / 401 / 400 / 2xx。备注:review.txt 里那段 provider HTTP 503 retry summary 是审查工具自身的重试噪声,不是可处理项。
- auth_session:/api/auth/phone/login 的 401(只来自「用户不存在」)改判为 phoneCodeLoginRejected{serverMessage},不再冒充「验证码错误或过期」 - auth_error:退役顶层变体 smsCodeRejected(唯一产生点就是这个错配的 401 分支) - 前端三个 catch 删掉 smsCodeRejected 分支;phoneCodeLoginRejected 文案统一为「验证码登录失败:<服务端原文>」,冒号改全角 - ts-rs 重新生成 ClientAuthError 联合;carrier 用例同步删掉 smsCodeRejected - 文档:ADR + decision-log 记录 401 归属与 smsCodeRejected 退役