diff --git a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs index 94fa85e50..4747f3201 100644 --- a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs +++ b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs @@ -9529,9 +9529,13 @@ fn membership_plan_row( ctx.db.profile_membership_plan().plan().find(&plan) } -/// 账号当前有效的并发上限:`profile_membership.plan` → 目录行 `concurrent_job_limit`。 +/// 账号当前有效的并发上限:会员行**当前有效档位** → 目录行 `concurrent_job_limit`。 /// -/// 缺会员行按 `Normal`(非会员);缺目录行**失败关闭**到 `Normal`(=1), +/// `profile_membership.plan` 保留最近一次购买的档位(到期后仍供展示「已失效的套餐」), +/// 所以这里必须像 [`build_profile_membership_snapshot_from_row`] 一样先按 `expires_at` 判断有效性: +/// 过期行回落 `Normal`,否则过期 `Max` 会继续拿到 `128`(不限并发)哨兵。 +/// +/// 缺会员行 / 已过期按 `Normal`(非会员);缺目录行**失败关闭**到 `Normal`(=1), /// `128` 哨兵由 [`is_unlimited_concurrency`] 在调用方解释。 pub(crate) fn effective_profile_concurrent_job_limit( ctx: &ReducerContext, @@ -9542,6 +9546,7 @@ pub(crate) fn effective_profile_concurrent_job_limit( .profile_membership() .user_id() .find(&owner_user_id.to_string()) + .filter(|row| active_membership_row_at(row, ctx.timestamp)) .map(|row| row.plan) .unwrap_or(RuntimeProfileMembershipPlan::Normal); membership_plan_row(ctx, plan)