Fix/修复登录用户参数等错误被上报 #567

Merged
k88936 merged 77 commits from fix/wrong-report into master 2026-10-02 15:02:27 +08:00
Member

close #562

close #562
k88936 added 4 commits 2026-10-01 15:44:46 +08:00
- 新增 ADR:命令错误按具体变体结构化(ts-rs 导出)、报告池只收没人处理的错误、前端按 type 分流不匹配文案
- 更新【技术方案】AGC错误报告与诊断上传:采集口径改为"只收没有调用方处理的错误",补命令变体分流与 clientApi 边界
- 更新共享记忆 decision-log:记录本次口径与影响范围
- 更新共享记忆 pitfalls:记录"输错密码被当成客户端缺陷上报"的现象、根因与判据
- 修正 DirectTurnError 模块注释中"命令边界只给字符串"的过期描述,改为结构化拒单载荷
- docs/README.md 登记新 ADR
- 新增 auth_error.rs:ClientAuthError 具体变体枚举,serde tag=type + ts-rs 导出,附变体名契约测试
- auth_session.rs 全量改为 Result<_, ClientAuthError>:请求/响应/凭据落盘/运行时会话安装按具体变体建模
- 路由语义由变体承担:会话 401/403 走 SessionAuthorityRejected/PermissionDenied,登录 401 保留服务端原因
- 400 变体按请求粒度命名(passwordEntryInputRejected 等),服务端只给 status+message,不做文案匹配
- 注册 auth_error 模块,生成 src/services/generated/ClientAuthError.ts
- 修订 ADR §2/§3:真故障由调用方包成 ClientActionError 交给错误池,不再把 rejection 留在无人接手的 Promise 上
- 说明 408/5xx/网络判定发生在调用方 catch,shouldCaptureClientError 随本 ADR 删除
- 同步技术方案、decision-log、pitfalls 的重抛口径与影响文件清单
AGC 前端错误分流基元:ClientActionError 与认证变体分类
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m9s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Successful in 2m3s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / AI game creator shell web tests (pull_request) Failing after 1m42s
Project CI / Native shell tests (pull_request) Successful in 5m41s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m10s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 11m9s
5013f4385f
- 新增 clientActionError.ts:ClientActionError 承载 source/action/page 上下文与 cause
- 新增 clientAuthError.ts:isClientAuthError 形状读取 + clientAuthErrorKind 的 input/session/fault 分类 + clientAuthErrorNotice 文案
- captureClientError 解包 ClientActionError 的上下文,并优先使用 cause 的栈信息
- getClientAuthErrorMessage 认识结构化拒绝,非 Error 形状不再字符串化成 [object Object]
- 新增 clientAuthError.test.ts,并在 errorReporting.test.ts 用解包用例替换 shouldCaptureClientError 用例
Author
Member
  • 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),所以是契约一致性问题,不是线上故障。
    • 处理:commit 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()。
    • 问题:续期时遇到 5xx(AuthServiceUnavailable)或响应不合法(AuthResponseMalformed)会被标成
      network,分类字段与事实不符。影响面目前有限:TS 侧 RustAuthStateView.errorKind 没有消费方
      (readClientAuthState 只看 status/user/apiBaseUrl,subscribeClientAuthState 同样不看),
      所以今天没有用户可见的错误提示走错分支;但一旦有人拿 errorKind 做重试/文案/埋点,就会踩坑。
    • 建议修法(跨 Rust 投影 + TS 映射,所以留给你决策):
      1. ClientAuthRefreshView 末尾加 error_kind: Option<String>(带 #[serde(default, skip_serializing_if = "Option::is_none")],
        与 ClientAuthStateView 同款),failed 分支填 Some(error.error_kind().to_string());
      2. read_client_auth_state 的 failed 分支改用 refresh.error_kind 而不是写死的 "network";
      3. TS 侧 refreshClientAuthSession 的本地类型加上 errorKind(现在 failed 只用 errorMessage,
        可按需透出),并同步 auth 相关文档/ADR 的投影字段说明;
      4. 补一条 Rust 单测:续期返回 5xx 时 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' 进入错误池,
      违背"预期业务拒绝永不进池"的约束。
    • 处理:commit 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(...) 会移动它。
    • 核实结果:该结论不成立。401/403 两个分支都是 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。
- [x] 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`),所以是契约一致性问题,不是线上故障。 - 处理:commit 90d37481a 在 docstring 里把"故意不校验"写成明确约定并给出理由:把 `status` 纳入校验, 只会让"缺 `status` 但 `message` 可用"的拒绝落回非结构化分支,Rust 文案被换成调用方兜底文案, 行为更差;将来真要用 `status` 判断时先补校验再用。不改判定逻辑,避免引入上面的回归。 - [x] 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()`。 - 问题:续期时遇到 5xx(`AuthServiceUnavailable`)或响应不合法(`AuthResponseMalformed`)会被标成 network,分类字段与事实不符。影响面目前有限:TS 侧 `RustAuthStateView.errorKind` 没有消费方 (`readClientAuthState` 只看 status/user/apiBaseUrl,`subscribeClientAuthState` 同样不看), 所以今天没有用户可见的错误提示走错分支;但一旦有人拿 `errorKind` 做重试/文案/埋点,就会踩坑。 - 建议修法(跨 Rust 投影 + TS 映射,所以留给你决策): 1. `ClientAuthRefreshView` 末尾加 `error_kind: Option<String>`(带 `#[serde(default, skip_serializing_if = "Option::is_none")]`, 与 `ClientAuthStateView` 同款),`failed` 分支填 `Some(error.error_kind().to_string())`; 2. `read_client_auth_state` 的 `failed` 分支改用 `refresh.error_kind` 而不是写死的 `"network"`; 3. TS 侧 `refreshClientAuthSession` 的本地类型加上 `errorKind`(现在 `failed` 只用 `errorMessage`, 可按需透出),并同步 auth 相关文档/ADR 的投影字段说明; 4. 补一条 Rust 单测:续期返回 5xx 时 `error_kind == "service"`(或直接测 `map_auth_failure` 的 分类到 `error_kind()` 的映射),避免以后又退回写死值。 注意:`ClientAuthRefreshView` 是命令返回的 wire 结构,虽然 TS 侧是手写类型不是 ts-rs 生成, 加字段仍属契约变更,按仓库约定要同批更新文档。 - [x] 3. 空文案的业务变体被误判成需要上报(原 [bug · low]) - 现状:`resolveClientAuthFailure` 之前是 `notice = clientAuthErrorNotice(structured)`,`notice !== null` 就算 notice 分支。 - 问题:`clientAuthErrorNotice` 对 `fault` 和"文案为空"都返回 `null`,两种情况被混在一起: 可识别的 `input`/`session` 变体只要 `message` 为空/全空白,就会掉进 `kind: 'report'` 进入错误池, 违背"预期业务拒绝永不进池"的约束。 - 处理:commit 8b50db989 改成用 `clientAuthErrorKind(structured) !== 'fault'` 判分流,提示文案用 `clientAuthErrorNotice(...) ?? getClientAuthErrorMessage(error, fallback)`;并在 `clientAuthErrorNotice` 的 docstring 注明 `null` 的双重含义、明示它不是分流判据; `clientAuthHost.test.ts` 增加"空文案业务变体 → notice + 兜底文案"的用例。 - [x] 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(...)` 会移动它。 - 核实结果:**该结论不成立**。401/403 两个分支都是 `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。
Author
Member
  • 1. direct_turn_error.rs 模块文档重新引入退役词「拒单」(原 [documentation · low])

    • 现状:模块文档第 19 行写「跨进程边界的拒单载荷是结构化的 DirectTurnEnqueueFailure」。
    • 问题:【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24 §1 明确「接单」「拒单」退役,
      不再出现在文档、注释、标识符与测试名里;这个新模块文档把词又带回了 DirectProject 域。
      同一验收下 tests/agentRuntimeModel.test.ts 还有 3 处(用例名 + 注释)也在用「拒单」。
    • 处理:commit 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,与事实不符。
    • 处理:commit d1e637b9f 把两条来源写进模块文档,并保留「Rust 侧任何地方都不解析这个字符串」的约定。
  • 3. AuthenticatedClient 两处失败处理重复(原 [maintainability · low])

    • 现状:handleSendCode 与 handleLoginSubmit 的 catch 里各写了一份「resolve → setLoginStatus →
      kind === 'report' 时 captureClientError(new ClientActionError(...))」,只有 fallback 文案与
      action 不同。
    • 问题:重复本身不致命,但错误池指纹依赖 action,两份实现将来容易漂移(改一份忘一份)。
    • 处理:commit 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 分流,没人再解析该前缀(AGC src/ 无消费方,Rust 侧也没有
      starts_with/contains("network-error")),用户却能看到「network-error: 无法连接登录服务…」。
      同文件的 AUTH_AUTHORITY_MESSAGE 早已去掉 authentication-required: 前缀,这里没跟上。
    • 处理:commit 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: 与这里无关,有各自的
      断言与用途,不动。
    • 处理:commit 402233ae9 去掉该条消息的前缀,文案变成「登录服务未返回新的续期凭据,已停止使用旧凭据」。
  • 6. refresh-failed 分支把 error_kind 写死成 network(原 [bug · low])— 条目已过期

    • 现状:条目指 read_client_auth_state 的 Ok(refresh) "failed" 分支写死
      error_kind: Some("network"),而 Err 分支用 error.error_kind()。
    • 核实:该字段与方法已在 commit 5d5aafe25 整体删除——ClientAuthStateView.error_kind 全仓 0 个读取方
      (TS 也从不读),所以不存在"传下来还是写死"的取舍,条目自然作废。现在 "failed" 分支只投影
      error_message,Err 分支同样只投影 error_message,两侧一致。
- [x] 1. direct_turn_error.rs 模块文档重新引入退役词「拒单」(原 [documentation · low]) - 现状:模块文档第 19 行写「跨进程边界的**拒单**载荷是结构化的 `DirectTurnEnqueueFailure`」。 - 问题:`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24` §1 明确「接单」「拒单」退役, 不再出现在文档、注释、标识符与测试名里;这个新模块文档把词又带回了 DirectProject 域。 同一验收下 `tests/agentRuntimeModel.test.ts` 还有 3 处(用例名 + 注释)也在用「拒单」。 - 处理:commit b202c7027 把模块文档与那 3 处统一改成「入队失败」。剩余两处不是违规: `direct_runtime/mod.rs:4344` 引用的是 2026-09-23 ADR 的**文件名**(改不了文件名), `codex_app_server/mod.rs:39` 的「每连**接单**行」是子串误报。 - [x] 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`,与事实不符。 - 处理:commit d1e637b9f 把两条来源写进模块文档,并保留「Rust 侧任何地方都不解析这个字符串」的约定。 - [x] 3. AuthenticatedClient 两处失败处理重复(原 [maintainability · low]) - 现状:`handleSendCode` 与 `handleLoginSubmit` 的 catch 里各写了一份「resolve → setLoginStatus → kind === 'report' 时 captureClientError(new ClientActionError(...))」,只有 fallback 文案与 `action` 不同。 - 问题:重复本身不致命,但错误池指纹依赖 `action`,两份实现将来容易漂移(改一份忘一份)。 - 处理:commit 698b3ffc9 抽出组件内的 `presentAuthFailure(error, fallback, action)`,两处 catch 各一行; 行为不变(appSurface 194 passed)。 - [x] 4. 网络失败文案仍带 `network-error:` 机器前缀(原 [maintainability · low]) - 现状:`AUTH_NETWORK_ERROR` / `AUTH_NETWORK_TIMEOUT` 以 `network-error:` 开头,经 `AuthNetworkUnavailable { message }` → 前端 `resolveClientAuthFailure` 原样显示。 - 问题:前端已按 `type` 分流,没人再解析该前缀(AGC `src/` 无消费方,Rust 侧也没有 `starts_with/contains("network-error")`),用户却能看到「network-error: 无法连接登录服务…」。 同文件的 `AUTH_AUTHORITY_MESSAGE` 早已去掉 `authentication-required:` 前缀,这里没跟上。 - 处理:commit c5153a7ed 去掉两条常量的前缀;`auth.suite.ts`、`clientAuthHost.test.ts` 的对应 fixture 与断言同步更新。 - [x] 5. 续期凭据缺失文案仍带 `result-unknown:` 前缀(原 [maintainability · low]) - 现状:`CookiePolicy::Require` 且没拿到新 cookie 时返回 `AuthResponseMalformed { message: "result-unknown: 登录服务未返回新的续期凭据…" }`。 - 问题:分流键已经变成变体 `authResponseMalformed`,该前缀没有解析方,只会出现在用户可见文案里。 注意:`resource_editor` / `platform_asset_upload` 等链路的 `result-unknown:` 与这里无关,有各自的 断言与用途,不动。 - 处理:commit 402233ae9 去掉该条消息的前缀,文案变成「登录服务未返回新的续期凭据,已停止使用旧凭据」。 - [x] 6. refresh-failed 分支把 `error_kind` 写死成 `network`(原 [bug · low])— 条目已过期 - 现状:条目指 `read_client_auth_state` 的 `Ok(refresh)` `"failed"` 分支写死 `error_kind: Some("network")`,而 `Err` 分支用 `error.error_kind()`。 - 核实:该字段与方法已在 commit 5d5aafe25 整体删除——`ClientAuthStateView.error_kind` 全仓 0 个读取方 (TS 也从不读),所以不存在"传下来还是写死"的取舍,条目自然作废。现在 `"failed"` 分支只投影 `error_message`,`Err` 分支同样只投影 `error_message`,两侧一致。
k88936 added 11 commits 2026-10-01 16:52:05 +08:00
- AuthenticatedClient 登录/发码 catch 改用 resolveClientAuthFailure:input/session 变体只给提示,fault 变体与未识别拒绝包成 ClientActionError 交给错误池
- clientAuth.ts 新增 resolveClientAuthFailure,判定只看结构化 type,不做文案匹配
- 删除 errorReporting.ts 的 shouldCaptureClientError,不再存在叶子自行判定要不要上报的口径
- clientAuthHost.test.ts 新增分流用例,auth.suite.ts 的登录失败用例改为结构化拒绝并断言提示文案
- resolveClientAuthFailure 改用 clientAuthErrorKind 判分流,不再用 clientAuthErrorNotice 是否为 null 当判据
- 空文案的 input/session 变体改为给兜底提示、不进错误池,保持"预期拒绝永不进池"的约束
- clientAuthErrorNotice 文档注明 null 同时表示 fault 与空文案,不能当分流判据
- clientAuthHost.test.ts 增加空文案业务变体的分流用例
- isClientAuthError 文档写明 status 故意不校验:无调用方读它,纳入校验会把可用文案换成兜底文案
- 需要 status 参与判断时先补校验再用
- 删除 ClientAuthError::error_kind():该分类由本次改动自行发明,全仓无消费方
- 删除 ClientAuthStateView.error_kind 字段及 4 个赋值点(含 read_client_auth_state 里写死的 "network")
- 删除 clientAuth.ts RustAuthStateView 的 errorKind 声明,该字段从未被读取
- 保留行为不变:失败仍以 error_message 投影,前端只看 status/user/apiBaseUrl/errorMessage
- direct_turn_error.rs 模块文档把"拒单载荷"改写为"入队失败载荷",与 2026-09-24 ADR 的词表一致
- agentRuntimeModel.test.ts 的用例名与注释同步改成"入队失败"
- 原文写"一处生成的 Display 文案"不准确:可留痕的宿主/环境失败走 record_direct_codex_failure 的 direct-codex-failure:v2 收口文案
- 文档改为写明两条来源(Display 与脱敏诊断收口文案),并保留"Rust 侧不解析该字符串"的约定
- 登录/发码两处 catch 的"分流 + 提示 + 上报"逻辑抽成 presentAuthFailure(error, fallback, action)
- 错误池指纹的 action 由调用点传入,避免新增认证动作时各自拼一套导致指纹漂移
- AUTH_NETWORK_ERROR / AUTH_NETWORK_TIMEOUT 不再带 network-error: 前缀,用户看到的就是完整中文提示
- 前端按 type 分流后不再解析该前缀,没有任何 Rust/TS 消费方依赖它
- auth.suite.ts 与 clientAuthHost.test.ts 的对应 fixture/断言同步更新
- AuthResponseMalformed 变体就是分流键,该前缀没有任何解析方,去掉后用户看到完整中文提示
- 该模块的 result-unknown 前缀只在本条出现,其它 result-unknown 属于 resource_editor 等其它链路
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
8fefd393b2
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
k88936 added 1 commit 2026-10-01 17:02:24 +08:00
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m33s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m12s
Project CI / Backend tests (pull_request) Successful in 3m55s
Project CI / Frontend tests (pull_request) Successful in 2m12s
Project CI / Native shell tests (pull_request) Successful in 6m29s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 10m27s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 11m29s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m53s
Project CI / Repository checks (pull_request) Successful in 2m34s
6b31d4971b
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
k88936 marked the pull request as ready for review 2026-10-01 17:07:40 +08:00
k88936 added 48 commits 2026-10-02 12:01:45 +08:00
- 新测试 mock 模块级 @tauri-apps/api/core invoke,从而能观察 report_client_error 是否被调用
- 覆盖:手机号或密码错误、发码限流不写错误池;网络不可达带 auth/login 上报一次
- 已用变异验证:把分流改成一律上报时,两条业务用例会失败
- 删除 clientAuthErrorKind / clientAuthErrorNotice,clientAuthError.ts 只留 isClientAuthError 形状读取
- 删除 resolveClientAuthFailure 与 ClientAuthFailureResolution,AuthenticatedClient 直接 switch 具体变体名
- 同步重写相关单测与 ADR 第 3 节前端分流描述
- 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) 分支里错误窄化成具名载荷类型
- 新增 ADR:认证命令统一经 invokeClientAuth 把结构化拒绝转成 ClientAuthFailure(payload 判别联合);判定只写在 catch 子句里,系统变体原样抛出经 unhandledrejection 入池,default: expectNever 编译期挡住漏接变体
- 修订【ADR】AGC命令错误结构化与错误报告口径 §3 与后果:撤销"未识别变体上调是故意的",改为编译期穷尽
- 技术方案同步认证命令的封装形态与两个 ADR 链接
- decision-log / pitfalls 记录第二轮口径:删除形状读取与文案回落层、Rust 失败不再降级成字符串
- docs/README.md 登记新 ADR
- refresh_session_inner 的非权威失败直接返回 Err(ClientAuthError),不再折成 error_message 字符串
- ClientAuthStateView 删除 error_message 字段,状态收窄为 authenticated / unauthenticated
- ClientAuthRefreshView 删除 error_message 与 failed 状态,authoritative 只在未登录时为 true
- read_client_auth_state 把续期失败原样透传给命令出口,不再投影出 unavailable 第三态
- 说明系统变体不读载荷、只原样抛出,但变体名必须逐个列出以让 default: expectNever 成立
- 业务 / 会话变体仍要求每个 case 用 as 取具名载荷类型
- 新增 ClientAuthFailure:payload 为 ts-rs 判别联合,context 固定 source=auth + 命令名,cause 为原始拒绝值
- clientAuth 的六个命令统一走 invokeClientAuth:结构化拒绝转成 ClientAuthFailure,非结构化拒绝原样抛出,不兜底文案
- 删除 clientAuthError.ts 的形状读取与 getClientAuthErrorMessage 文案回落层
- AuthenticatedClient 的两个 catch 内联 19 个变体的 switch:业务 / 会话变体原样展示载荷 message,系统变体原样抛出经 unhandledrejection 入池,default: expectNever 保证漏接变体编译失败
- hydrateAuth 失败先离开 loading 态;系统变体不再伪装成 unavailable 投影
- ClientAuthState 收窄为 authenticated / unauthenticated,ClientAuthRefreshResult 收窄为 refreshed / unauthenticated / stale
- platformSession 续期只保留 refreshed / unauthenticated / stale 三条路径
- check-config 的 App 调用扫描登记 invokeClientAuth,继续静态证明命令名可达
- 测试:新增 clientAuthFailure 载体与编译期穷尽用例、jsdom unhandledrejection 桥;删除 clientAuthError.test.ts;clientAuthHost / authFailureReporting / authSurface 跟改新口径
- 共享记忆记录 check-config 扫描形态新增 invokeClientAuth
- 先等系统变体经 unhandledrejection 抛出(并校验抛出物带 Rust 原文),再断言登录页没有该文案
- 原写法在首个 waitFor tick 就因 queryByText 返回 null 而通过,没真正验证拒绝落地后的文案
- 合成事件的 reason 改为访问器:有监听方读过就算有人接手;没人读则交回安装前的监听器(含 vitest 失败上报)
- 新增 unhandledRejectionBridge.test.ts 钉住两条路径:无人接手会交回、window 读过则不再交回
- 避免桥把无关的未处理拒绝静默吞掉
- map_auth_failure 对 AuthRoute::SendCode 的 401/403 仍归 UnexpectedRejection(不是权威失效、不是业务输入)
- 依据:api-server 的 send_phone_code 只返回 400 与 5xx,401/403 出现即为契约异常,应带上文进上报池
- ADR §2 去掉 type 存在性检查:Rust 与 TS 同包发布、形状由 ts-rs 保证,异常形状属 Tauri/Rust 缺陷,仍由 switch 的 default 抛出去上报
- requireInvoke 移到 try 之外,认证桥未安装的错误保持原样抛出
- Error.message 只从拒绝值读可展示字符串,不是分流判据
- ADR §1 记录保留 19 个具名载荷类型的理由:它们是每个分支 as 的目标;内联 struct 变体虽能少 19 个文件,但分支就拿不到可 as 的具名类型
- invokeClientAuth 不再检查 type 是否存在:认证命令的拒绝按 ts-rs 映射直接转成 ClientAuthFailure
- requireInvoke 提前到 try 之外:认证桥未安装是我们自己的失败关闭错误,保持原样抛出
- Error.message 仍只取拒绝值里的可展示字符串,只做展示,不参与分流
- 测试:新增"桥未安装保持原样抛出"与"Tauri 意外形状也按映射包装"两条判据;authFailureReporting 的意外形状用例改为断言 source=auth + 命令名
- ADR 认证失败载体:删除手写 ClientAuthFailure 类,拒绝装进 ClientActionError,cause 即判别联合
- ADR 命令错误结构化:同步第 3 节对 JS 载体的描述
- 技术方案:认证命令封装形态改为 ClientActionError + cause
- 决策记录:影响范围去掉 clientAuthFailure.ts
- 排障记忆:载体、判据与关联文件路径同步
- 删除 clientAuthFailure.ts,invokeClientAuth 改为把拒绝装进已有 ClientActionError,cause 即 ts-rs 生成的 ClientAuthError
- 三处 catch 改为 instanceof ClientActionError 后取 error.cause 分流,每个 case 用 as 取具名载荷
- 认证载体用例改名 clientAuthErrorCarrier.test.ts,断言改为 ClientActionError.cause
- clientAuthHost 用例与 auth_session.rs 注释同步新载体
- auth_session.rs 注释改为 invokeClientAuth 装进 ClientActionError,cause 是判别联合
- ADR 认证失败载体:ClientActionError 只保留 error 字段,不读 message、不塞 context、message 留空
- 技术方案与决策记录同步载体形状与 error.error 分流
- 排障记忆:判据改为全局 unhandledrejection 显式上下文
- ADR、技术方案、决策记录同步 error 字段的显式类型标注
- ADR、README、技术方案、决策记录与排障记忆统一新类名
- auth_session.rs 注释同步
- 第一轮影响范围里的 clientActionError.ts 改为 clientAuthErrorWrapper.ts
- clientActionError.ts 改名 clientAuthErrorWrapper.ts,类名同步为 ClientAuthErrorWrapper,error 字段标注 ClientAuthError
- invokeClientAuth 改为薄包装:不读变体 message、不塞 context,只把拒绝原样装成 JS Error
- 三处 catch 改用 error.error 分流,每个 case 继续用 as 取具名载荷
- captureClientError 从 error 字段取原始错误;上下文只认显式入参
- 载体、host、错误上报与 appSurface 用例同步新载体与新默认文案
- 更新 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 } & 载荷类型
- AuthenticatedClient 的三个 catch 列全新变体:无字段变体直接给固定文案,带载荷变体先 as 再读 serverMessage
- 系统变体逐个列出后原样抛出,default 仍用 expectNever 把漏接变体卡在编译期
- clientAuth/ClientAuthErrorWrapper 注释去掉对 message 字段的假设
- 载体用例覆盖变体全集:无字段变体取固定文案,带载荷变体 as 后读 serverMessage/status/detail
- 上报判据新增 passwordLoginRejected:服务端 400 原文只展示、不写错误池
- 网关用例改用 authNetworkUnreachable / serverAddressNotHttps 等新变体名
- 更新 AGC 命令错误结构化 ADR:顶层只放调用方要分流的类别,可枚举细分收进类型化枚举 reason 字段,不再拆成几十个顶层变体、也不用字符串
- 更新 AGC 认证失败 JS 侧载体 ADR:带载荷分支先 as 取具名类型,reason 是枚举时再 switch(payload.reason),两处 default 都用 expectNever
- 同步错误报告技术方案、README 索引、决策记录与踩坑的拍平变体口径
- auth_error.rs:顶层从拍平变体收敛为类别变体,服务地址/网络失败/响应契约破损各占一个变体,不再拆成 7+2+5 个
- auth_error.rs:新增 ServerAddressReason、AuthNetworkReason、AuthResponseInvalidReason 三个 ts-rs 枚举,payload 改持类型化 reason
- auth_session.rs:校验与映射改用 server_address_rejected / response_invalid 收口,按类型化 reason 构造
- ClientAuthError 联合收敛为类别变体 + 类型化 reason,无字段变体仍是 { type }
- 新增 AuthNetworkFailure/AuthNetworkReason/AuthResponseInvalid/AuthResponseInvalidReason/ServerAddressRejected/ServerAddressReason
- 删除拍平变体后不再生成的 AuthResponseServerRejected 载荷文件
- AuthenticatedClient 三个 catch 把 authNetworkFailure/authResponseInvalid 收成一个分支,服务地址 7 种失败与响应契约 5 种破损改为在 payload.reason 上二次 switch
- 每个带载荷分支先 as 取自己的具名类型,reason/default 用 expectNever 让漏接 reason 也在编译期失败
- 系统变体仍原样 throw,经全局 unhandledrejection 交给错误池
- 载体用例:authNetworkFailure 与 authResponseInvalid 改为内层 reason switch,覆盖全部 reason 与 default expectNever
- 网关、上报、页面用例改用 authNetworkFailure 等新变体名与类型化 reason 载荷
- 新增可枚举细分不读服务端文案的分流断言
- 删除「后果与边界」里不再准确的「用户可见行为」一行(系统失败不是只进错误池)
- 该节改为说明系统变体先按载荷原始事实给一行可见反馈,再原样抛出
- hydrateAuth 的 catch 恢复改动前的可见反馈:登录状态检查超时等非载体错误先写 authCheckError/loginStatus 再原样抛出
- 系统变体先按类型化载荷(reason/status/detail/serverMessage)拼出提示文案,再原样抛出交全局 unhandledrejection 上报
- appSurface 用例改为断言启动超时后登录页显示原因并保留「重试登录状态检查」
- handleSendCode/handleLoginSubmit 的 catch 在抛出前把系统失败原因写进 loginStatus,离开「正在发送验证码」「正在登录」
- 系统变体按类型化载荷拼出提示文案后再原样抛出,恢复改动前「显示 + 上报」的可见行为
- 本地运行时超时等非载体错误同样先写 loginStatus 再抛出
- appSurface 用例改为断言系统变体文案落到登录页且仍经 unhandledrejection 抛出
- 「后果与边界」改为「必须同时改所有 catch 的 switch」,避免数量写法随实现漂移
- 更新 JS 侧载体 ADR:ClientAuthErrorWrapper 构造时把整份载荷 JSON.stringify 进 Error.message,上报事件拿到变体名与载荷,事件指纹按变体区分
- 记录 Tauri 缺陷抛真 Error 时序列化只有 {},但上报链路对真 Error 优先用其自身 message/stack
- 同步决策记录与技术方案的载体口径,不再写 Error.message 留空
- ClientAuthErrorWrapper 构造时 super(JSON.stringify(error)):结构化拒绝在上报事件里是变体名与载荷,不再落成未知客户端错误
- errorReporting 注释同步:载体 message 已带载荷,Tauri 缺陷抛出的真 Error 仍优先用其自身 message/stack
- 载体用例断言 message 为载荷序列化;Tauri 缺陷抛真 Error 时是 {},但上报仍优先用其自身 message/stack
- 上报用例的 message 断言改为序列化结果,并新增真 Error 优先分支用例
- errorReporting 的嵌套三元改为 if/else 赋值,行为不变,只提高可读性
- 启动超时用例改回 getByRole + disabled=false,避免渲染但禁用的重试按钮也能通过
- 业务变体用例的 Promise.reject 改为 throw,与相邻用例风格一致、不与已 resolved 混淆
- 两处重复的桥接安装、reason 收集与拆卸收成 captureWindowRejections()
- 顺带修正系统变体用例里过期的载体 message 注释(现在 message 是载荷序列化)
- 用 rawListeners 记录原始监听器,拆卸时把 once 包装按 process.once 装回,普通监听器仍按 process.on 装回
- 未被 window 消费时调用 once 包装包裹的原函数,避免脱离 emitter 直调包装
- 注释记录安装期间新增监听器排在恢复监听器之前的既有边界
- AuthenticatedClient 三个 catch 的 default 分支在 rethrow 前补一行泛化提示,界面不再停在无解释状态
- 上报判据用例同步断言 Tauri 意外拒绝形状也会显示这条提示
- 命令错误结构化 ADR 补 429 按路由判定的口径:发码是频控、登录是验证码次数耗尽、其余才落未识别拒绝
- 决策记录同步追加 2026-10-02 的 429 归类
- map_auth_failure 的 429 改为按路由判定:SendCode 仍是 SmsCodeThrottled,PhoneLogin 映射为 PhoneCodeLoginRejected(用户可修正、不进错误池),其余路由才落 UnexpectedRejection
- 补用例覆盖登录 429 与其它路由 429;map_auth_failure 注释同步新口径
- 命令错误结构化 ADR:本机凭据/运行时会话 IO 失败不再把原始错误文本放进载荷,原始 error 只写本地日志
- 认证失败 JS 侧载体 ADR 与决策记录同步去掉 detail 口径,并追加 2026-10-02 的变体改无字段决定
本机IO失败去掉detail载荷
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m20s
Project CI / Backend tests (pull_request) Failing after 21s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m10s
Project CI / Frontend tests (pull_request) Successful in 2m27s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m50s
Project CI / Native shell tests (pull_request) Successful in 6m25s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
53e2889bbd
- Rust 错误枚举:clientSessionPersistFailed / runtimeSessionInstallFailed 改无字段变体,删除会带 AppData 路径的 payload 结构体
- auth_session:本机凭据/运行时会话 IO 失败改成写本地日志后返回无字段变体,不再把原始 error 放进载荷
- 前端:登录检查/发码/登录三处 catch 去掉 as 取 detail,改用本操作的固定文案
- ts-rs 重新生成 ClientAuthError 联合,删除两个已不存在的载荷绑定文件
- 补充 auth_error 用例:两个变体序列化为仅含 type 的无字段形状
Author
Member
  • 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

    • 处理:429 按 route 分流,AuthRoute::PhoneLogin → PhoneCodeLoginRejected { server_message }(复用现有业务变体、不新增),其余路由仍 UnexpectedRejection;补 3 条 auth_session 用例。
  • 9. 已修(订正版:文档 d518bb2ce + 代码 7128e865c):本机 IO 失败的 detail

    • 首版(53e2889bb)把它从载荷删掉,是错的:你原始问题是它被贴上登录页,修法应是"不展示",不是"不进载荷"。
    • 订正后:Rust 侧 clientSessionPersistFailed / runtimeSessionInstallFailed 是带 detail 的载荷变体,detail 装原始 error,供调用方分流与报告诊断。
    • 前端:登录检查/发码/登录三处 catch 不展示 detail,各用本操作的固定文案(本机登录凭据读写失败 / 本机运行时会话安装失败,带各自前缀)。
    • 报告:载荷随 ClientAuthErrorWrapper 序列化进 Error.message 入池;路径等由报告侧 sanitize 换成 <path>,不靠 UI 脱敏。
    • 本地日志:同一 error 仍经 app_log!(落盘前过 sanitize_diagnostic_message)记一行。
    • 绑定:重跑 ts-rs 恢复两个载荷文件。
  • 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。
- [x] 1. 已修(5f76e3bb5):AGC 认证 catch 的 default 分支补可见提示 - 处理:`hydrateAuth` / `sendCode` / `login` 三个 catch 的 `default:` 在 rethrow 前先写一行泛化文案「登录服务返回了未识别的错误,请重试或提交错误报告」,结构化映射外的 Tauri/IPC 失败不再停在空白状态;仍然原样抛出走上报。 - 测试:`authFailureReporting.test.tsx` 补一条钉住可见提示。 - [x] 3. 已修(0803e035b):errorReporting.ts 取文案的嵌套三元改 if/else 赋值 - 处理:`let errorValue: Error` + if/else 链,行为不变;errorReporting 12 个用例通过。 - [x] 5. 已修(325ebda7e):auth.suite.ts 启动超时用例的重试按钮断言退化成存在性 - 处理:改回 `toHaveProperty('disabled', false)`,恢复「可点重试」回归覆盖;appSurface 194 passed。 - [x] 6. 已修(7528ba011):unhandledRejectionBridge 用 `rawListeners` 恢复监听器 - 处理:恢复时按 `.listener` 判断走 `process.once`;未被消费时直调包装内原函数。**顺序问题保留**(安装期间新增的监听器仍排在前面),已写进注释。 - [x] 7. 已修(7278023cb):auth.suite.ts 业务变体用例用 `return Promise.reject(...)` 混风格 - 处理:改成 `throw {…}` + `return { status: 'unauthenticated' }`。 - [x] 8. 已修(b54b3794f,文档 f8c32e634):登录路由 429 落到 UnexpectedRejection 的系统变体口径 bug - 处理:429 按 route 分流,`AuthRoute::PhoneLogin` → `PhoneCodeLoginRejected { server_message }`(复用现有业务变体、不新增),其余路由仍 `UnexpectedRejection`;补 3 条 auth_session 用例。 - [x] 9. 已修(订正版:文档 d518bb2ce + 代码 7128e865c):本机 IO 失败的 `detail` - 首版(53e2889bb)把它从载荷删掉,是**错的**:你原始问题是它被贴上登录页,修法应是"不展示",不是"不进载荷"。 - 订正后:Rust 侧 `clientSessionPersistFailed` / `runtimeSessionInstallFailed` 是带 `detail` 的载荷变体,`detail` 装原始 error,供调用方分流与报告诊断。 - 前端:登录检查/发码/登录三处 catch **不展示 detail**,各用本操作的固定文案(`本机登录凭据读写失败` / `本机运行时会话安装失败`,带各自前缀)。 - 报告:载荷随 `ClientAuthErrorWrapper` 序列化进 `Error.message` 入池;路径等由报告侧 sanitize 换成 `<path>`,不靠 UI 脱敏。 - 本地日志:同一 error 仍经 `app_log!`(落盘前过 `sanitize_diagnostic_message`)记一行。 - 绑定:重跑 ts-rs 恢复两个载荷文件。 - [x] 10. 已修(da4645f55):auth.suite.ts 桥接安装 / reason 收集 / 拆卸重复 - 处理:抽成 `captureWindowRejections()`(返回 `{ reasons, uninstall }`),顺带修正系统变体用例里过期注释。 - [x] 11. 已修(e1e8725da,文档 d518bb2ce):AuthClientInitFailed 丢掉 reqwest 原始错误 - 处理:`authClientInitFailed` 从 unit 变体改成带 `detail` 的载荷变体,`build_client()` 失败时把 reqwest 原始错误放进 `detail`;导出 `AuthClientInitFailed` 绑定;carrier 用例按带载荷变体 `as` 取 `detail`。 - 界面仍用固定文案(`创建登录网络客户端失败`),不展示 detail。
k88936 added 4 commits 2026-10-02 12:20:11 +08:00
- 命令错误结构化 ADR:本机 IO / 网络客户端构建失败的原始 detail 留在载荷(调用方分流与报告诊断用),界面只用固定文案,路径交给报告侧 sanitize
- 认证失败 JS 侧载体 ADR:带 detail 的本机失败是例外,不把原始错误文本贴到界面上
- 决策记录:追加 2026-10-02 口径,取代上一轮"本机 IO 失败不进载荷"
- 错误枚举:clientSessionPersistFailed / runtimeSessionInstallFailed 恢复成带 detail 的载荷变体,重新导出两个 ts-rs 绑定文件
- auth_session:IO 失败把原始 error 放进 detail 的同时,仍记一行本地日志
- 前端维持不展示 detail,继续用本操作的固定文案(登录检查 / 发码 / 登录)
- carrier 用例改回对两个载荷变体 as 取 detail,钉住"带载荷变体先取具名类型"
- 错误枚举:authClientInitFailed 从 unit 变体改成带 detail 的载荷变体,导出 AuthClientInitFailed 绑定
- auth_session:build_client 失败时把 reqwest 原始错误放进 detail,不再丢弃
- carrier 用例按带载荷变体 as 取 detail
- 前端维持固定文案,不把 detail 贴到界面上
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 53s
Project CI / Backend tests (pull_request) Failing after 21s
Project CI / Frontend tests (pull_request) Successful in 2m25s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m45s
Project CI / Native shell tests (pull_request) Successful in 5m48s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 10m34s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 11m18s
e4b15d3bbf
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
#	docs/project-memory/shared-memory/pitfalls.md
Author
Member

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 原样抄进生成的 .ts JSDoc。
    • 处理:改源文档为 `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(() => () => {}) 只盖订阅建立、盖不到回调。
    • 我的判断:与现行 ADR 口径冲突——「形状完全信任 tauri + ts-rs 映射、不做运行时嗅探」,且抛出的异常正好经 unhandledrejection 进错误池。恢复守卫会把坏载荷静默降级成「未登录」,更难发现。补充:这个 RustAuthStateView 是手写类型(不是 ts-rs 生成),信任边界比错误枚举弱。
    • 留你定:维持「信任映射、坏了就抛」,还是给这个手写事件载荷加守卫。
  • 6. 三处 catch 的 switch 重复 ~150 行

    • 现状:hydrateAuth / handleSendCode / handleLoginSubmit 各有一份完整的 failure→文案 switch。
    • 这条是上轮 review item 2 的重复建议,你当时明确拒绝(ADR:判定与上报只留在各自 catch)。
    • 留你定:维持现状,或只抽「纯算文案、不决定是否上报」的函数。
  • 7. /api/auth/phone/login 的 401 被标成 SmsCodeRejected(已修 836f8a734,保持 string 设计)

    • 核实:服务端 401 只来自 PhoneAuthError::UserNotFound(phone_auth.rs:410);验证码错误/失效/过期都是 400,已由 PhoneCodeLoginRejected { serverMessage } 带原文。
    • 处理:401 归 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 是审查工具自身的重试噪声,不是可处理项。

# review 处理结果(2026-10-02 第三轮) 8 条:自动修 3 条(1/2/3),你拍板修 4 条(4/7/8 + 7 按你定的 string 设计收尾)。剩 2 条(5/6)留给你。 - [x] 1. 生成绑定的 JSDoc 抄了 Rust 语法(已修 74e7209c3) - 现状:`auth_error.rs` 里 `Option<String>` 字段的文档写 `` `None` ``,`AuthResponseInvalid.server_message` 写 `` `AuthResponseInvalidReason::ServerRejected` ``;ts-rs 原样抄进生成的 `.ts` JSDoc。 - 处理:改源文档为 `` `null` `` / `` `serverRejected` `` 并重生成;顺手修了同一类的另外 3 处(`PasswordLoginRejected` / `SendCodeRejected` / `PhoneCodeLoginRejected`)。 - [x] 2. auth.suite 启动超时用例断言太松(已修 3d4c42901) - 处理:先 `toHaveLength(1)` 再断言这一条是含「检查登录状态超时」的 `Error`;无关拒绝不再被静默吞掉。 - [x] 3. 拒绝桥交回原监听器时 `this` 为空(已修 b0f9eb229) - 处理:改 `Reflect.apply(listener.listener ?? listener, process, [reason, promise])`,补一条「this === process」用例。 - [x] 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 也算被消费」的用例。 - [x] 5. `subscribeClientAuthState` 去掉了 payload 空值守卫 - 现状:`view.status === 'authenticated'` 直接解引用;`view?.status === 'authenticated' && view.user && view.apiBaseUrl` 这个守卫在 `ce54c729e` 里被删。 - 问题:`subscribeTauriEvent` 把 payload 直接 `as RustAuthStateView`,坏载荷会在事件回调里抛 TypeError,而 trailing `.catch(() => () => {})` 只盖订阅建立、盖不到回调。 - **我的判断:与现行 ADR 口径冲突**——「形状完全信任 tauri + ts-rs 映射、不做运行时嗅探」,且抛出的异常正好经 `unhandledrejection` 进错误池。恢复守卫会把坏载荷静默降级成「未登录」,更难发现。补充:这个 `RustAuthStateView` 是**手写**类型(不是 ts-rs 生成),信任边界比错误枚举弱。 - **留你定**:维持「信任映射、坏了就抛」,还是给这个手写事件载荷加守卫。 - [x] 6. 三处 catch 的 `switch` 重复 ~150 行 - 现状:`hydrateAuth` / `handleSendCode` / `handleLoginSubmit` 各有一份完整的 failure→文案 `switch`。 - **这条是上轮 review item 2 的重复建议,你当时明确拒绝**(ADR:判定与上报只留在各自 catch)。 - **留你定**:维持现状,或只抽「纯算文案、不决定是否上报」的函数。 - [x] 7. `/api/auth/phone/login` 的 401 被标成 `SmsCodeRejected`(已修 836f8a734,保持 string 设计) - 核实:服务端 401 只来自 `PhoneAuthError::UserNotFound`(`phone_auth.rs:410`);验证码错误/失效/过期都是 **400**,已由 `PhoneCodeLoginRejected { serverMessage }` 带原文。 - 处理:401 归 `PhoneCodeLoginRejected { serverMessage }`;退役顶层变体 `SmsCodeRejected`(唯一产生点就是那个错配分支);前端三个 catch 删掉该分支,文案统一「验证码登录失败:<服务端原文>」(冒号改全角);docs + 绑定 + 用例同步。 - [x] 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 是审查工具自身的重试噪声,不是可处理项。
k88936 added 7 commits 2026-10-02 14:00:40 +08:00
- auth_error:服务端原文缺失说明从 None 改成 TS 读得懂的 null(PasswordLoginRejected / SendCodeRejected / PhoneCodeLoginRejected)
- auth_error:AuthResponseInvalid.server_message 的说明从 AuthResponseInvalidReason::ServerRejected 改成线上字面量 serverRejected
- ts-rs 重新生成 4 份载荷绑定
- auth.suite:启动超时用例先断言恰好捕获 1 条拒绝,再断言它是含「检查登录状态超时」的 Error
- 原来的 some(...) 只看有没有命中,无关 unhandledrejection 会被这段捕获静默吞掉、测不出回归
- unhandledRejectionBridge:交回原监听器改用 Reflect.apply 把 this 绑成 process,和 Node EventEmitter 一致,依赖 this 的原监听器不再拿到 undefined
- 补一条用例钉住 this 语义
- 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 退役
- auth_session:拿到 status 后 response.text() 失败不再一律报网络不可达;非 2xx 按状态码走既有分类(serverMessage 为 None,如 503 → authServiceUnavailable),只有 2xx 响应没收完才归传输层故障
- 抽出 classify_unreadable_body 并补单测(503 / 会话 401 / 400 / 2xx)
- 决策记录追加该口径
- unhandledRejectionBridge:合成事件优先用 PromiseRejectionEvent(jsdom 没这个构造器时退回普通 Event),再把 reason / promise 都挂成实例访问器,jsdom 分支下 promise 也可读
- 判定调整为读 reason 或 promise 任一个都算 window 侧接手(原来只认 reason)
- 补一条用例:事件带 promise 且读 promise 也算被消费
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 2m23s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 1m48s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 2m14s
Project CI / Repository checks (pull_request) Failing after 36s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m49s
Project CI / Frontend tests (pull_request) Successful in 4m7s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m41s
Project CI / Native shell tests (pull_request) Failing after 5m46s
f756b52d8c
# Conflicts:
#	docs/project-memory/shared-memory/pitfalls.md
k88936 added 1 commit 2026-10-02 14:37:46 +08:00
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m47s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m35s
Project CI / Backend tests (pull_request) Successful in 5m5s
Project CI / Native shell tests (pull_request) Successful in 6m7s
Project CI / Frontend tests (pull_request) Successful in 2m19s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m55s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m55s
Project CI / Repository checks (pull_request) Successful in 2m32s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 12m54s
29a21f480c
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
#	docs/project-memory/shared-memory/pitfalls.md
k88936 added 1 commit 2026-10-02 15:02:12 +08:00
Merge remote-tracking branch 'origin/master' into fix/wrong-report
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m59s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 2m9s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m23s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 3m6s
Project CI / Frontend tests (pull_request) Successful in 3m31s
Project CI / Repository checks (pull_request) Successful in 3m34s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m57s
Project CI / Backend tests (pull_request) Successful in 6m48s
Project CI / Native shell tests (pull_request) Successful in 7m29s
264b99914f
# Conflicts:
#	docs/project-memory/shared-memory/pitfalls.md
k88936 merged commit 70aeb79be6 into master 2026-10-02 15:02:27 +08:00
k88936 deleted branch fix/wrong-report 2026-10-02 15:02:27 +08:00
Sign in to join this conversation.