Files
junhong_cmp_fiber/openspec/changes/fix-package-activation-starvation/proposal.md
break b3499adfca 固化七月迭代审计治理进展以隔离线上热修
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 参数类型错误未通过。
2026-08-03 09:47:22 +08:00

37 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
功能 ID`feature-505-package-activation-recovery`
生产环境已第二次出现“原主套餐已过期、队首待生效主套餐仍停留在 `status=0`”的问题。只读数据证明旧孤儿扫描每轮固定读取的 100 条记录全部仍有占位主套餐,真实孤儿因此永久无法进入恢复窗口;七月迭代分支需要按其现有同步接续能力和卡观测 Outbox 架构消除同类缺陷,避免未来覆盖发布后继续复现。
## What Changes
- 将孤儿恢复改为由 PostgreSQL 在限流前完成“每个载体只选队首套餐”和“排除仍有 `status IN (1,2)` 占位主套餐”的筛选,避免固定前 100 条造成永久饥饿,并删除逐条占位检查的 N+1 查询。
- 七月分支的过期接续不再投递 `package:queue:activation` Asynq 任务:旧套餐过期事务提交后,直接调用现有 `ActivateNextPendingMainPackage` 推进队首套餐。
- 孤儿恢复同样直接调用 `ActivateNextPendingMainPackage`,复用载体级 Redis 锁、状态幂等、购买条款快照和激活事务。
- 套餐激活事务继续原子写入现有 `card.observation.series.requested` Outbox由七月分支公共 Outbox 可靠驱动后续实名、流量和网络观测;不新增第二种套餐激活事件。
- 收紧结果日志:只有实际推进套餐状态时记录激活成功,锁冲突、占位阻塞、等待实名和幂等跳过分别记录稳定原因。
- 按用户明确要求,本 Change 不新增、修改或运行自动化测试;使用全量构建、只读 SQL、查询计划和运行日志完成验证。
## Capabilities
### New Capabilities
无。
### Modified Capabilities
- `package-queue-activation`:将七月迭代的过期接续与孤儿恢复收口为同步应用调用,并补充真实孤儿公平扫描和既有卡观测 Outbox 一致性要求。
## Impact
- **适用分支**:仅 `Iteration/7-11`;本 Change 不描述、不实施、不引用 `main` 线上热修。
- **架构通道**:主通道为套餐生命周期复杂写,沿用 `Polling Handler → Package Activation Service → GORM transaction`;辅助通道为 PostgreSQL 候选查询、Redis 载体锁和既有卡观测 Outbox。完整边界只覆盖“旧套餐过期后接续队首套餐及孤儿恢复”不迁移购买、实名、流量、退款和停复机用例。
- **主要代码**`internal/polling/package_activation_handler.go`、必要时最小调整 `internal/service/package/activation_service.go` 的结果日志;复用现有 `internal/infrastructure/cardobservation/series_event.go` 和公共 Outbox 装配。
- **数据库**:不新增表、字段、索引或迁移;只调整现有 `tb_package_usage` 查询和调用顺序。
- **API/前端**无接口、DTO、路由和前端改动。
- **依赖**:不新增依赖,继续使用 GORM、PostgreSQL、Redis、Zap 及七月分支已有 Outbox。
- **性能**:孤儿扫描从“最多 100 条候选 + 最多 100 次占位查询”收敛为一次数据库候选查询和最多 100 次有业务意义的同步激活调用;使用 `EXPLAIN (ANALYZE, BUFFERS)` 验证查询成本。
- **审计与可靠性**Audit Event N/A系统自动生命周期推进`tb_package_usage` 是状态权威事实Integration Log N/A本修复不新增外呼复用现有 `card.observation.series.requested` Outbox不新增事件类型。
- **验证**:不写自动化测试;执行 `gofmt`、现有静态检查、`go build ./...`、只读 SQL、查询计划和日志核验。