## 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`,按当前分支的同步接续与卡观测 Outbox 架构独立实施。 - **架构通道**:主通道为套餐生命周期复杂写,沿用 `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、查询计划和日志核验。