Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。 Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。 Confidence: medium Scope-risk: broad Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。 Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。 Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
5.0 KiB
5.0 KiB
MODIFIED Requirements
Requirement: 当前主套餐过期后自动激活下一个
系统 SHALL 在生效中或已用完的主套餐到期时,先提交旧主套餐和关联加油包的状态变化,再通过现有套餐激活应用能力同步推进同一载体的队首待生效主套餐;过期接续路径 MUST NOT 在旧主套餐事务内投递 package:queue:activation 任务。
成功激活队首套餐时,系统 SHALL 在套餐激活事务内同步追加现有 card.observation.series.requested Outbox;任一写入失败时激活事务 MUST 回滚。
Scenario: 旧主套餐提交过期后同步接续队首套餐
- WHEN 轮询处理一个已到期的
status=1或status=2主套餐,且同载体存在队首待生效套餐 - THEN 系统先提交旧主套餐
status=3和关联加油包失效的数据库事务 - AND 事务提交后调用现有套餐激活应用能力
- AND 应用能力在新事务内重新校验占位状态并激活队首套餐
- AND 过期接续路径不投递
package:queue:activation任务
Scenario: 激活与卡观测 Outbox 原子提交
- WHEN 队首套餐满足激活条件
- THEN 系统在同一事务内把套餐推进为
status=1并写入激活、到期及适用的重置时间 - AND 同一事务追加稳定的
card.observation.series.requestedOutbox - AND Outbox 写入失败时套餐状态回滚为待生效
Scenario: 过期事务失败时不接续
- WHEN 更新旧主套餐或级联失效加油包的事务失败
- THEN 事务回滚
- AND 系统不得调用队首套餐激活能力
- AND 下一轮过期扫描仍可重新处理该旧主套餐
Scenario: 提交后进程退出由孤儿扫描恢复
- WHEN 旧主套餐过期事务已经提交,但进程在调用接续能力前退出
- THEN 待生效套餐保持
status=0 - AND 下一轮孤儿扫描识别该载体并直接调用同一套餐激活能力
Scenario: 无待生效套餐时保持无套餐状态
- WHEN 旧主套餐过期事务提交后,同载体不存在有效的
status=0主套餐 - THEN 套餐激活应用能力不推进任何记录
- AND 载体进入无主套餐状态并沿用既有停机检查流程
ADDED Requirements
Requirement: 孤儿待生效套餐必须公平恢复
系统 SHALL 在数据库中先选出每个卡或设备载体唯一的队首待生效主套餐,并排除仍存在 status IN (1,2) 占位主套餐的载体,最后才对真实孤儿结果执行单轮上限;系统 MUST NOT 先从全库待生效记录截取固定窗口再逐条判断占位状态。
队首顺序 SHALL 为 priority ASC, created_at ASC, id ASC。单轮最多处理 100 个真实孤儿载体,同一载体不得占用多个恢复名额。每个候选 SHALL 直接调用现有套餐激活应用能力,不投递 package:queue:activation 任务。
Scenario: 前一百条待生效记录均有占位套餐
- WHEN 全库排序靠前的 100 条待生效记录所属载体均存在
status IN (1,2)主套餐,且窗口之后存在一条无占位主套餐的真实孤儿 - THEN 数据库先排除这 100 条非孤儿记录
- AND 真实孤儿进入本轮恢复候选
- AND 系统直接调用套餐激活应用能力推进其队首套餐
Scenario: 同一载体存在多条待生效套餐
- WHEN 一个无占位主套餐的载体存在多条
status=0主套餐 - THEN 本轮只选择
priority最小、其次created_at最早、最后id最小的一条 - AND 该载体只占用一个恢复名额
- AND 后续套餐继续保持排队状态
Scenario: 生效中和已用完套餐均属于占位
- WHEN 待生效套餐所属载体仍有
status=1或status=2的主套餐 - THEN 该载体不得进入孤儿恢复候选
- AND 待生效套餐保持排队状态
Scenario: 单轮真实孤儿超过上限
- WHEN 数据库存在超过 100 个真实孤儿载体
- THEN 系统按稳定顺序选择前 100 个载体调用接续能力
- AND 未选中的真实孤儿在后续轮询中继续具备候选资格
Requirement: 同步接续结果必须准确记录
系统 SHALL 仅在套餐激活应用能力返回 activated=true 时记录本轮激活成功。锁冲突、占位状态变化、等待实名和已完成幂等结果不得记录为新的激活成功。
Scenario: 本轮成功激活
- WHEN 指定载体无占位主套餐且队首套餐满足激活条件
- THEN 应用能力返回
activated=true - AND 轮询记录包含套餐使用记录、载体和触发来源的成功结果
Scenario: 激活锁冲突
- WHEN 同步接续未获得载体级 Redis 激活锁
- THEN 应用能力返回现有套餐激活冲突错误
- AND 本轮不得记录激活成功
- AND 下一轮过期或孤儿扫描继续恢复
Scenario: 条件暂不满足
- WHEN 应用能力复检发现载体已有占位主套餐或队首仍等待实名
- THEN 应用能力不修改套餐状态
- AND 轮询记录明确原因
- AND 后续扫描仍可重新判断