docs(会员计费): 同步评审第 13–20 项的契约与实现口径
Project CI / Backend tests (pull_request) Failing after 28s
Project CI / Native shell tests (pull_request) Failing after 2m6s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m17s
Project CI / Frontend tests (pull_request) Failing after 45s
Project CI / Repository checks (pull_request) Failing after 18s
Project CI / AI game creator shell web tests (pull_request) Failing after 41s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m19s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 4m55s

- 后端架构文档:充点商品写入口收敛为 9 个字段,退役列保留且写死 0;首充赠送统一为 0
- 技术设计 §2.5/§5.7:补 ProfileMembershipStatusToken 与 5 个 token 枚举的 ts-rs 生成说明
- 技术设计 §11.4:AgcModelResolveError 增 NoModelForTier,模型解析错误统一走 typed AppError
- 决策记录:新增第 13–20 项收口条目,含影响范围与验证结论
This commit is contained in:
2026-10-04 21:56:26 +08:00
parent 14c147722a
commit 264f214fe5
3 changed files with 27 additions and 9 deletions
@@ -151,9 +151,11 @@ pub struct ProfileMembershipPlan {
- 新增 `RuntimeProfileMembershipModelAccess { Basic, Full }`(仅新增字段,本期不执行)。
- 新增 `RuntimeProfileMembershipChangeKind { Purchase, Upgrade }`(订单快照用,替代字符串)。
- `shared-contracts` 新增 wire 侧 `ProfileMembershipPlanToken` / `ProfileMembershipCycleKindToken` /
`ProfileMembershipModelAccessToken` / `ProfileMembershipChangeKindToken`,把 DTO 里原来的裸
`String`(后台 upsert 请求、订单变更快照、会员 / 档位 / 报价响应)收口为 typed enum;
与模块枚举通过 `From` 双向映射,JSON wire 值仍是小写 token,未知 token 在反序列化阶段被拒。
`ProfileMembershipStatusToken` / `ProfileMembershipModelAccessToken` / `ProfileMembershipChangeKindToken`,
把 DTO 里原来的裸 `String`(后台 upsert 请求、后台会员详情、订单变更快照、会员 / 档位 / 报价响应)
收口为 typed enum;与模块枚举通过 `From` 双向映射,JSON wire 值仍是小写 token,未知 token 在
反序列化阶段被拒。这 5 个 token 枚举统一 `#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]`
导出到 `packages/shared/src/contracts/generated/`,前端只 re-export,不再手写字符串联合类型。
- **删除** `RuntimeProfileMembershipTier { Normal, Month, Season, Year, Starter, Basic, Pro, Ultimate }`
及其 `as_str()`(会员侧零存量,用户已授权破坏性变更)。
- `RuntimeProfileWalletLedgerSourceType` **末尾追加** `MembershipUpgradeGrant`
@@ -429,6 +431,11 @@ flowchart TD
`cargo test -p shared-contracts --features ts-bindings export_bindings` 生成
`packages/shared/src/contracts/generated/ProfileMudPointBalanceResponse.ts`,
`packages/shared/src/contracts/runtime.ts` 只 re-export 成 `ProfileMudPointBalance`。
- 会员 token(`profileMembershipPlanToken` / `profileMembershipCycleKindToken` /
`profileMembershipStatusToken` / `profileMembershipModelAccessToken` /
`profileMembershipChangeKindToken`)同样由 ts-rs 导出为字符串联合(5 个生成文件),
`packages/shared/src/contracts/runtime.ts` 与 `apps/admin-web/src/api/adminApiTypes.ts`
改为生成类型别名 / re-export,删掉本地手写字面量。
- `u64` 字段加 `ts(type = "number")`:JSON 走数字,不用 `bigint`,避免序列化与前端类型分叉。
- `npm run check:generated-bindings` 以「重新生成并与工作区逐字节比较」做门禁,防止 Rust 改了而生成文件没重跑。
- 需要展示用派生字段(如 `totalPoints` 合计)时,在前端 `utils/mudPoints.ts` 里派生,不改 DTO。
@@ -610,12 +617,12 @@ flowchart TD
### 11.4 实现落点(2026-10-03 编码约定)
- **`module-runtime`**:
- 新增 `agc_model_access.rs`:权限档 `AgcModelAccess`(`basic` / `full`,`Default = Basic`)、越权错误码常量 `MODEL_NOT_AVAILABLE_FOR_PLAN`、解析错误 `AgcModelResolveError::{Unavailable, NotAvailableForPlan}`,以及 `RuntimeProfileMembershipModelAccess → AgcModelAccess` 的映射。
- 新增 `agc_model_access.rs`:权限档 `AgcModelAccess`(`basic` / `full`,`Default = Basic`)、越权错误码常量 `MODEL_NOT_AVAILABLE_FOR_PLAN`、解析错误 `AgcModelResolveError::{Unavailable, NoModelForTier, NotAvailableForPlan}`(`Unavailable` 与 `NoModelForTier` 复用 `MODEL_UNAVAILABLE` 码,分别映射 422 / 503),以及 `RuntimeProfileMembershipModelAccess → AgcModelAccess` 的映射。
- `AgcModel` 末位追加 `#[serde(default)] access: AgcModelAccess`(存量目录 JSON 缺字段即 `Basic`,失败开放);`AgcModelCatalog` 新增 `available_models_for(access)`、`default_model_id_for(access)`、`resolve_requested_for(requested, access)`。
- **默认模型语义**:优先取目录登记的 `default_model_id`;当它不在该档可用集合里时,回退到目录顺序里第一个可用项;该档一个可用模型都没有时返回 `Unavailable`。
- **`spacetime-module`**:`external_generation_job` 追加 btree 索引 `(owner_user_id, status)`(`by_external_generation_job_owner_status`);`claim_external_generation_jobs_tx` 按账号解析 `concurrent_job_limit` 并在认领时过滤;上限解析走 `profile_membership.plan` → `profile_membership_plan.concurrent_job_limit`,缺会员行 / 缺目录行失败关闭到 `Normal`(=1)。
- **`api-server`**:新增 `llm/model_access.rs`,调专用只读 procedure `get_profile_agc_model_access_and_return`(只读会员账期投影 + 档位目录 `model_access`,不刷新、不写库)并映射到 `AgcModelAccess`;`GET /api/llm/models` 按档过滤,`/api/llm/responses`、`/api/llm/chat/completions`、`/api/llm/anthropic/*` 全部按档解析,越权返回 403 + 错误码 `MODEL_NOT_AVAILABLE_FOR_PLAN`。
- **后台**:`AdminAgcModel` 与后台「AGC 模型」页新增 `access` 字段(`basic` / `full`,缺省 `basic`),GET 回读、PUT 写入;**不做**自动播种或代码内清单。
- **`api-server`**:新增 `llm/model_access.rs`,调专用只读 procedure `get_profile_agc_model_access_and_return`(只读会员账期投影 + 档位目录 `model_access`,不刷新、不写库)并映射到 `AgcModelAccess`;`GET /api/llm/models` 按档过滤,`/api/llm/responses`、`/api/llm/chat/completions`、`/api/llm/anthropic/*` 全部按档解析,越权返回 403 + 错误码 `MODEL_NOT_AVAILABLE_FOR_PLAN`,该档无可用模型 / 所选模型不可用分别返回 503 / 422 + `MODEL_UNAVAILABLE`。`resolve_llm_router_client` 返回 `Result<_, AppError>`,会员目录 `503 + MODEL_ACCESS_UNAVAILABLE` 与上述模型解析错误都通过 typed `AppError` 透传,不再拍平成中文。
- **后台**:`AdminAgcModel` 与后台「AGC 模型」页新增 `access` 字段(`basic` / `full`,缺省 `basic`),GET 回读、PUT 写入;`AdminAgcModel.access` 与 `AdminUpsertProfileMembershipPlanRequest.model_access` 一样用 `ProfileMembershipModelAccessToken`,未知 token 反序列化即拒;**不做**自动播种或代码内清单。
- **性能说明(2026-10-03 已落地)**:账号档位走专用只读 procedure `get_profile_agc_model_access_and_return`(`9e397fe9c`),不再触发 `get_profile_recharge_center` 的账期 / 免费点刷新;`api-server` 于 `b0dc94b46` 切到该读。认领按账号折叠并发查询见 `c3c3fc5a2`。缺会员行 / 已过期按 `Normal`,缺档位目录行失败关闭到 `Basic`。
- **列表契约(2026-10-03 修订)**:`shared-contracts` 新增 `llm_catalog.rs` 承载 `LlmModelsResponse` / `LlmModelSummary` / `LlmUnavailableModel` / `LlmModelUnavailableReason`(`plan_required` / `disabled` / `unknown`);`AgcAgentMode` / `AgcModelProtocol` 从 `module-runtime` 迁入 `shared-contracts` 并由 `module-runtime` re-export;整个目录 DTO 与枚举用 `#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]` 导出到 `packages/shared/src/contracts/generated/`,`revision` 标 `#[ts(as = "f64")]`,AGC 前端从 `@genarrative/shared` 根导入,删掉手写的 `ClientLlmModel`。
- **分桶来源**:`module-runtime` 新增 `AgcModelCatalog::unavailable_models_for(access)`(保持目录顺序,返回 `(&AgcModel, LlmModelUnavailableReason)`),`api-server` 只做 DTO 映射;`models` 仍走 `available_models_for`;`unavailableModels` 恒定下发(无内容时为空数组)。