文档:系统类失败载荷保留原始错误
- 命令错误结构化 ADR:本机 IO / 网络客户端构建失败的原始 detail 留在载荷(调用方分流与报告诊断用),界面只用固定文案,路径交给报告侧 sanitize - 认证失败 JS 侧载体 ADR:带 detail 的本机失败是例外,不把原始错误文本贴到界面上 - 决策记录:追加 2026-10-02 口径,取代上一轮"本机 IO 失败不进载荷"
This commit is contained in:
@@ -46,9 +46,11 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
|
|||||||
Java 的嵌套 `switch`,仍不碰文案)。顶层与 `reason` 的 `default` 都用 `expectNever`,漏接变体或漏接
|
Java 的嵌套 `switch`,仍不碰文案)。顶层与 `reason` 的 `default` 都用 `expectNever`,漏接变体或漏接
|
||||||
`reason` 都是**编译错误**。**不假设所有变体都有同一个字段**,也不做任何文案匹配。
|
`reason` 都是**编译错误**。**不假设所有变体都有同一个字段**,也不做任何文案匹配。
|
||||||
- Rust **不预拼用户可见文案**:载荷只装原始事实(类型化的 `reason`、服务端 400 的原文 `serverMessage`、
|
- Rust **不预拼用户可见文案**:载荷只装原始事实(类型化的 `reason`、服务端 400 的原文 `serverMessage`、
|
||||||
HTTP 状态码),服务端没给原文就是 `None`;前缀与句式由前端调用方在自己的 catch 分支按当前操作拼接。
|
HTTP 状态码、本机 IO / 网络客户端构建失败的原始 `detail`),服务端没给原文就是 `None`;前缀与句式由
|
||||||
本机凭据 / 运行时会话的 IO 失败不再把原始错误文本放进载荷(它曾把 AppData 路径带进登录页与上报包),
|
前端调用方在自己的 catch 分支按当前操作拼接。**原始错误必须留在载荷里**(调用方据此分流,报告包据此
|
||||||
原始 `error` 只写本地日志;对应变体是无字段变体,前端给固定文案。
|
诊断),同时另记一行本地日志;但 `detail` **不直接贴在界面上**:`clientSessionPersistFailed` /
|
||||||
|
`runtimeSessionInstallFailed` / `authClientInitFailed` 三个 catch 用本操作的固定文案,路径等敏感片段
|
||||||
|
由报告侧的 sanitize 换成占位符。
|
||||||
- `#[tauri::command]` 的 `Err` 直接携带该枚举(Tauri 2 的 `InvokeError(pub serde_json::Value)` 支持结构化错误)。
|
- `#[tauri::command]` 的 `Err` 直接携带该枚举(Tauri 2 的 `InvokeError(pub serde_json::Value)` 支持结构化错误)。
|
||||||
这是 DirectProject 已有的做法(`enqueue_direct_codex_turn -> Result<(), DirectTurnEnqueueFailure>`),不是新约定。
|
这是 DirectProject 已有的做法(`enqueue_direct_codex_turn -> Result<(), DirectTurnEnqueueFailure>`),不是新约定。
|
||||||
- 变体按**可判定的事实**命名。服务端 400 只提供 `status + message`(`AppError.code` 仍是通用
|
- 变体按**可判定的事实**命名。服务端 400 只提供 `status + message`(`AppError.code` 仍是通用
|
||||||
|
|||||||
@@ -109,8 +109,9 @@ catch (error) {
|
|||||||
细分 `reason` 时,再 `switch (payload.reason)` 在类型化枚举上分流。无字段的 `case` 直接用本 catch 的
|
细分 `reason` 时,再 `switch (payload.reason)` 在类型化枚举上分流。无字段的 `case` 直接用本 catch 的
|
||||||
固定文案。不写跨变体的通用读取,也不让 Rust 预拼上下文。前缀取自当前 catch 的操作语义(登录、发码、
|
固定文案。不写跨变体的通用读取,也不让 Rust 预拼上下文。前缀取自当前 catch 的操作语义(登录、发码、
|
||||||
启动检查各自可以不同),等价于 Java 的 `catch (PasswordLoginRejected e)`。系统变体先按载荷里的原始
|
启动检查各自可以不同),等价于 Java 的 `catch (PasswordLoginRejected e)`。系统变体先按载荷里的原始
|
||||||
事实(`reason` / `status` / `serverMessage`)给一行可见反馈,再原样抛出;变体名必须逐个
|
事实(`reason` / `status` / `serverMessage`)给一行可见反馈,再原样抛出;带 `detail` 的本机失败是例外,
|
||||||
列出,`default` 的 `expectNever` 才成立。
|
只用固定文案,`detail` 只用于分流与诊断、不贴到界面上——变体名必须逐个列出,`default` 的
|
||||||
|
`expectNever` 才成立。
|
||||||
- 业务 / 会话变体:Rust 只给可判定事实(无字段变体连字段都没有;带载荷变体给类型化 `reason` /
|
- 业务 / 会话变体:Rust 只给可判定事实(无字段变体连字段都没有;带载荷变体给类型化 `reason` /
|
||||||
`serverMessage` / `status`),调用方在自己的 catch 里补上本次操作的上下文前缀(例如
|
`serverMessage` / `status`),调用方在自己的 catch 里补上本次操作的上下文前缀(例如
|
||||||
「服务器地址非法: 远程地址必须使用 https」、「登录失败: 密码长度需要在 6 到 128 位之间」)。
|
「服务器地址非法: 远程地址必须使用 https」、「登录失败: 密码长度需要在 6 到 128 位之间」)。
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
- 决策(2026-10-01,判定位置与出口):要不要上报只由 catch 子句里的 `switch (failure.type)` 判,`failure = error.error`;无字段业务 / 会话变体用本 catch 的固定文案,带载荷变体先 `as` 取自己的具名类型、再用它自己的 `reason` / `serverMessage` / `status` 拼上本次操作的上下文前缀(`reason` 是枚举时再 `switch (payload.reason)`);Rust 不预拼用户可见文案、服务端原文缺失就是 `null`(无兜底文案)。系统变体原样 `throw` 经全局 `unhandledrejection` 入池(`captureClientError` 用 `instanceof` 解包 `error` 字段取原始错误),`default: expectNever(failure)` 让漏接变体编译失败。取代"未识别变体上调是故意的"。
|
- 决策(2026-10-01,判定位置与出口):要不要上报只由 catch 子句里的 `switch (failure.type)` 判,`failure = error.error`;无字段业务 / 会话变体用本 catch 的固定文案,带载荷变体先 `as` 取自己的具名类型、再用它自己的 `reason` / `serverMessage` / `status` 拼上本次操作的上下文前缀(`reason` 是枚举时再 `switch (payload.reason)`);Rust 不预拼用户可见文案、服务端原文缺失就是 `null`(无兜底文案)。系统变体原样 `throw` 经全局 `unhandledrejection` 入池(`captureClientError` 用 `instanceof` 解包 `error` 字段取原始错误),`default: expectNever(failure)` 让漏接变体编译失败。取代"未识别变体上调是故意的"。
|
||||||
- 决策(2026-10-01,Rust 侧不再降级):`refresh_session_inner` 的非权威失败直接 `Err(ClientAuthError)`,`ClientAuthStateView` / `ClientAuthRefreshView` 删除 `errorMessage`,续期结果删除 `failed`;`ClientAuthState` 收敛为 `authenticated | unauthenticated`,`ClientAuthRefreshResult` 收敛为 `refreshed | unauthenticated | stale`。
|
- 决策(2026-10-01,Rust 侧不再降级):`refresh_session_inner` 的非权威失败直接 `Err(ClientAuthError)`,`ClientAuthStateView` / `ClientAuthRefreshView` 删除 `errorMessage`,续期结果删除 `failed`;`ClientAuthState` 收敛为 `authenticated | unauthenticated`,`ClientAuthRefreshResult` 收敛为 `refreshed | unauthenticated | stale`。
|
||||||
- 追加(2026-10-02,429 按路由判定):`/api/auth/phone/login` 验证码错误次数耗尽返回的 429 是用户可修正的输入问题,映射为 `phoneCodeLoginRejected`(复用现有业务变体、不进错误池);发码路由仍是 `smsCodeThrottled`,其余路由的 429 仍是 `unexpectedRejection`。
|
- 追加(2026-10-02,429 按路由判定):`/api/auth/phone/login` 验证码错误次数耗尽返回的 429 是用户可修正的输入问题,映射为 `phoneCodeLoginRejected`(复用现有业务变体、不进错误池);发码路由仍是 `smsCodeThrottled`,其余路由的 429 仍是 `unexpectedRejection`。
|
||||||
- 追加(2026-10-02,本机 IO 失败去 detail):`clientSessionPersistFailed` / `runtimeSessionInstallFailed` 改为无字段变体,删除会带 AppData 路径的 `detail`;原始 error 只经 `sanitize_diagnostic_message` 写本地日志,登录页用固定文案,上报载荷只保留变体名。
|
- 追加(2026-10-02,系统类失败保留原始错误载荷):`clientSessionPersistFailed` / `runtimeSessionInstallFailed` / `authClientInitFailed` 都带 `detail: string`(原始 error),既让调用方有机会分流处理,也让报告包带够诊断信息;原始 error 同时经 `app_log!`(落盘前过 `sanitize_diagnostic_message`)记一行本地日志。`detail` 不贴到界面上:三个 catch 用本操作的固定文案(登录检查 / 发码 / 登录各自不同)。取代上一版"本机 IO 失败不进载荷、原始 error 只进日志"。
|
||||||
- 影响范围(2026-10-01 第二轮):`apps/ai-game-creator-shell/src/services/{clientAuth.ts,platformSession.ts}`(`clientAuthError.ts` 删除)、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src-tauri/src/auth_session.rs`、对应 vitest 用例。
|
- 影响范围(2026-10-01 第二轮):`apps/ai-game-creator-shell/src/services/{clientAuth.ts,platformSession.ts}`(`clientAuthError.ts` 删除)、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src-tauri/src/auth_session.rs`、对应 vitest 用例。
|
||||||
|
|
||||||
## 2026-10-01 DirectProject 审批拒绝原因留痕
|
## 2026-10-01 DirectProject 审批拒绝原因留痕
|
||||||
|
|||||||
Reference in New Issue
Block a user