类型化(会员错误):会员业务拒绝改用机器可读 error_code

- module-runtime 新增 RuntimeProfileMembershipErrorCode 与 RuntimeProfileMembershipDomainError
- 会员报价 / 充值中心 / 后台档位 upsert 的 procedure 结果末尾追加 error_code
- spacetime-module 在拒绝点显式赋码,中文 error_message 逐字保持不变
- spacetime-client 新增 SpacetimeClientError::ProcedureRejected 并映射生成绑定
- api-server 删除会员中文前缀分类,改按 error_code 返回 400,基础设施错误仍 502
- 重生成 module_bindings,并同步设计文档 5.8 与决策日志
This commit is contained in:
2026-10-04 12:41:26 +08:00
parent 75766824f6
commit 68c8544a15
14 changed files with 601 additions and 143 deletions
@@ -9585,3 +9585,12 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 影响范围:`server-rs/crates/shared-contracts/src/admin.rs`、`server-rs/crates/api-server/src/admin_recharge.rs`、`apps/admin-web/src/{api/adminApiTypes.ts,config/membershipDisplay.ts,pages/AdminMembershipPlanPage.tsx,pages/AdminRechargeOrderPage.tsx,components/AdminUserDetailDialog.tsx}`、`scripts/admin-web-fake-api.mjs`、设计文档 §5.6/§10.2、本文件。
- 验证:`cargo check -p api-server`;`npm run admin-web:typecheck`;`npx vitest run apps/admin-web` → 26 files / 248 tests passed(新增「会员订单在发放泥点列展示会员变更快照」及用户详情会员断言)。
- 边界:`/admin/api/*` 不在 `/api/external/v1` OpenAPI 门禁内,本次未补接口级契约测试,会员快照只在管理端页面用例层面取证。
## 2026-10-03 会员链路错误分类改为机器可读错误码(typed error)
- 背景:`api-server/src/runtime_profile.rs` 的 `is_runtime_profile_membership_domain_error` 用中文 `error_message` 前缀 / 精确匹配决定返回 400 还是 502;文案一改(或新增拒绝点)就静默退化,且分类逻辑与 module 的错误文案跨层重复。
- 决策(错误码随 procedure 结果透传):`module-runtime` 新增 `RuntimeProfileMembershipErrorCode`(`UpgradeRejected` / `NotMember` / `AlreadyActive` / `CycleKindLocked` / `MembershipExpired` / `StateChanged` / `PlanNotPurchasable` / `PlanCatalogMissing` / `ProductIdUnparsable` / `MembershipProductRetired` / `InvalidPlanConfig`)与内部 `RuntimeProfileMembershipDomainError { code, message }`;三个会员 procedure 结果(升级报价、充值中心 / 下单、后台档位 upsert)末尾追加 `error_code: Option<...>`,拒绝点显式赋码,不再做字符串分类。
- 决策(客户端错误类型):`spacetime-client` 新增 `SpacetimeClientError::ProcedureRejected { code, message }`;`api-server` 据此返回 400(provider `runtime-profile`),基础设施错误保持 502。字段校验类错误不产码,仍落 502。
- 决策(行为变化):`会员目录缺少档位 ...` 与「已迁移的会员商品」原先落 502,现在明确 400;兑换码链路本轮未改造,仍保留一个文案匹配函数。procedure result 新增字段属 wire 契约变更,要求 module 与 api-server 同版本部署。
- 影响范围:`server-rs/crates/module-runtime/src/domain.rs`、`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`、`server-rs/crates/spacetime-client/src/{active.rs,active/mapper/runtime_profile.rs}`、生成绑定、`server-rs/crates/api-server/src/{runtime_profile.rs,asset_billing.rs,editor_project.rs}`、`docs/technical/【技术设计】泥点三池与会员计费后端设计-2026-10-02.md` §5.8、本文件。
- 验证:`npm run spacetime:generate`;`cargo check --workspace`;`api-server runtime_profile::tests` 41 passed;`module-runtime --lib membership::` 39 passed;`npm run check:encoding`、`npm run check:spacetime-schema`、`cargo fmt -- --check`、`git diff --check` 通过。
@@ -433,6 +433,24 @@ flowchart TD
- `npm run check:generated-bindings` 以「重新生成并与工作区逐字节比较」做门禁,防止 Rust 改了而生成文件没重跑。
- 需要展示用派生字段(如 `totalPoints` 合计)时,在前端 `utils/mudPoints.ts` 里派生,不改 DTO。
### 5.8 会员错误码契约(typed error,已落地)
- 会员链路的业务拒绝不再靠中文 `error_message` 前缀分类。`module-runtime` 新增
`RuntimeProfileMembershipErrorCode`(`UpgradeRejected` / `NotMember` / `AlreadyActive` /
`CycleKindLocked` / `MembershipExpired` / `StateChanged` / `PlanNotPurchasable` /
`PlanCatalogMissing` / `ProductIdUnparsable` / `MembershipProductRetired` / `InvalidPlanConfig`),
在拒绝点显式产出,`RuntimeProfileMembershipUpgradeQuoteProcedureResult` /
`RuntimeProfileRechargeCenterProcedureResult` / `RuntimeProfileMembershipPlanAdminProcedureResult`
末尾追加 `error_code: Option<RuntimeProfileMembershipErrorCode>`(`error_message` 保留人读文案)。
- `spacetime-client` 的 `SpacetimeClientError` 新增
`ProcedureRejected { code, message }`;`api-server` 的 `runtime_profile` 映射据此返回 400
(`provider = runtime-profile`),基础设施错误保持 502。字段校验类错误(如 user_id 缺失)
不产出 `error_code`,仍按字符串错误落到 502。
- 行为变化:`会员目录缺少档位 ...` 与「已迁移的会员商品」原先落到 502,现在有明确错误码,统一 400。
- 兼容边界:procedure result 是 wire 结构,新增字段要求 module 与 api-server 同版本部署;
旧 module 不返回 `error_code` 时,新 client 绑定反序列化会失败。兑换码链路本轮未改造,
`api-server` 仍保留一个兑换码文案匹配函数。
---
## 6. 计算规则(后端唯一真相源)