固化七月迭代审计治理进展以隔离线上热修
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 参数类型错误未通过。
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-03
|
||||
120
openspec/changes/fix-package-activation-starvation/design.md
Normal file
120
openspec/changes/fix-package-activation-starvation/design.md
Normal file
@@ -0,0 +1,120 @@
|
||||
## Context
|
||||
|
||||
线上诊断得到旧孤儿扫描窗口 `scanned_count=100`、`skipped_as_occupied=100`、`waiting_realname=0`。这证明“先取前 100 条待生效记录,再在 Go 中逐条排除占位载体”的实现会让窗口之外的真实孤儿永久饥饿。
|
||||
|
||||
七月迭代分支虽然仍保留旧 `enqueueActivationTask`,但已经具备更合适的本地接续边界:`ActivationService.ActivateNextPendingMainPackage` 负责载体锁、队首选择、条款快照、状态事务和提交后复机;实际激活事务还会原子追加 `card.observation.series.requested` Outbox。套餐状态推进本身是本地数据库用例,不需要再经过一次 Asynq 才能执行。
|
||||
|
||||
本设计仅适用于 `Iteration/7-11`,不包含任何 `main` 分支兼容、纯 Asynq 热修或跨分支移植决策。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 每轮最多 100 个恢复名额只用于无占位主套餐的真实孤儿载体,同一载体只选择队首套餐。
|
||||
- 旧主套餐过期事实提交后,直接调用七月分支现有接续能力推进下一套餐。
|
||||
- 孤儿恢复直接复用同一接续能力,不再创建第二条异步激活链。
|
||||
- 套餐激活状态与既有卡观测 Outbox 保持同一事务,后续观测继续可靠投递。
|
||||
- 日志准确区分实际激活、幂等、锁冲突和业务条件未满足。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不涉及 `main`,不生成可向 `main` cherry-pick 的修复提交。
|
||||
- 不新增套餐激活 Outbox 事件、消费者、迁移或队列类型。
|
||||
- 不删除仍可能被其他旧入口使用的 `TaskTypePackageQueueActivation` 和 Handler;只停止过期接续与孤儿恢复继续走该路径。
|
||||
- 不修改购买、优先级分配、实名激活、流量扣减、退款失效和停复机规则。
|
||||
- 不新增 API、DTO、路由、依赖、数据库字段或索引。
|
||||
- 按用户要求,不新增、修改或运行自动化测试。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1:数据库先选择真实孤儿队首,再执行 LIMIT
|
||||
|
||||
孤儿查询通过 GORM 执行 PostgreSQL CTE/窗口查询:
|
||||
|
||||
1. 从 `status=0 AND master_usage_id IS NULL AND deleted_at IS NULL` 按卡或设备载体分组。
|
||||
2. 每个载体按 `priority ASC, created_at ASC, id ASC` 选择唯一队首。
|
||||
3. 通过相关 `NOT EXISTS` 排除仍有 `status IN (1,2)` 主套餐的载体。
|
||||
4. 对真实孤儿稳定排序后执行 `LIMIT 100`。
|
||||
|
||||
查询结果直接进入同步接续循环,删除逐条 `Count` 和 Go map 二次分组。
|
||||
|
||||
**拒绝:仅增大 LIMIT。** 数据增长后仍会复现,并放大 N+1。
|
||||
|
||||
**拒绝:游标扫描所有待生效记录。** 需要维护扫描状态,复杂度高于一次正确查询。
|
||||
|
||||
### 决策 2:过期事务提交后同步调用现有接续服务
|
||||
|
||||
`processExpiredPackage` 的事务继续负责旧主套餐 `status=3` 和关联加油包 `status=4`。事务成功提交后,Handler 调用 `ActivateNextPendingMainPackage(ctx, carrierType, carrierID)`,不再调用 `activateNextPackage` 投递 Asynq。
|
||||
|
||||
若进程在两次事务之间退出,数据库会留下“无占位主套餐 + 有待生效套餐”的持久状态,下一轮修正后的孤儿扫描会直接调用同一接续能力恢复,最长增加一个轮询周期。
|
||||
|
||||
**拒绝:事务内直接投递 Asynq。** 消费者可能早于事务提交读取旧状态。
|
||||
|
||||
**拒绝:新增套餐激活 Outbox。** 状态推进是本地同步用例,现有服务已经具备完整事务;新增事件只会形成第二套调度和消费状态。需要可靠投递的后续卡观测已经由激活事务写入现有 Outbox。
|
||||
|
||||
### 决策 3:孤儿恢复同步调用同一接续服务
|
||||
|
||||
每个真实孤儿候选只携带载体类型和 ID,调用 `ActivateNextPendingMainPackage`。服务在事务内重新检查占位状态和队首,避免依赖扫描快照执行写操作;载体级 Redis 锁防止多个轮询实例并发激活。
|
||||
|
||||
锁冲突返回现有 `CodePackageActivationConflict`。轮询记录原因后结束本次候选,下一轮扫描继续恢复,不依赖 Asynq 重试。
|
||||
|
||||
### 决策 4:复用现有卡观测 Outbox
|
||||
|
||||
成功激活仍通过 `appendActivationObservation` 在套餐激活事务内追加稳定事件:
|
||||
|
||||
- 事件类型:`card.observation.series.requested`
|
||||
- 稳定事件 ID:`card-observation:package-usage:{usage_id}:activated`
|
||||
- 同步类型:实名、流量、网络
|
||||
- 资源:实际卡或设备载体
|
||||
|
||||
Outbox 写入失败时套餐激活事务回滚,避免状态已生效但后续观测请求丢失。不新增套餐激活事件、Relay 或消费者。
|
||||
|
||||
### 决策 5:日志以实际结果为准
|
||||
|
||||
`ActivateNextPendingMainPackage` 已返回 `activated bool`。调用方仅在 `activated=true` 时记录本轮成功;`false,nil` 表示幂等或条件暂不满足,记录明确结果;锁冲突和数据库错误按错误路径记录,不伪造成功。
|
||||
|
||||
### 决策 6:公共能力决定
|
||||
|
||||
- **Audit Event:N/A。** 系统自动生命周期推进,不是人工敏感操作。
|
||||
- **Domain Ledger:N/A。** `tb_package_usage` 是套餐状态、激活和到期的权威事实。
|
||||
- **Integration Log:N/A。** 本修复不新增外部请求。
|
||||
- **Outbox:复用。** 成功激活继续在同一事务写 `card.observation.series.requested`;不新增事件类型。
|
||||
|
||||
实施时增量维护 `.scratch/tech-global-audit/审计覆盖基线.md`。
|
||||
|
||||
### 决策 7:不包含自动化测试
|
||||
|
||||
按用户明确要求,本 Change 不创建、修改或运行自动化测试。验证使用:
|
||||
|
||||
- `gofmt` 和现有静态检查;
|
||||
- `go build ./...`;
|
||||
- 只读候选 SQL及 `EXPLAIN (ANALYZE, BUFFERS)`;
|
||||
- 日志顺序和激活结果核验;
|
||||
- 激活事务与卡观测 Outbox 记录的一致性查询。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[风险] 候选查询扫描大量待生效记录** → 用现有索引和查询计划验证;没有性能证据前不新增索引。
|
||||
- **[风险] 旧套餐提交后、接续调用前进程退出** → 下一轮真实孤儿扫描从数据库权威状态恢复。
|
||||
- **[风险] 多实例重复处理同一载体** → 服务内载体级 Redis 锁、事务内占位复检和状态幂等共同收敛。
|
||||
- **[风险] 同步接续增加单轮耗时** → 单轮最多 100 个真实孤儿,接续仅执行本地 Redis/数据库事务;记录耗时后再决定是否需要批次调整。
|
||||
- **[权衡] 保留旧 Asynq Handler** → 避免扩大未触碰调用方;本 Change 只让过期和孤儿两条路径停止使用它。
|
||||
- **[权衡] 不新增自动化测试** → 遵循用户边界,以构建、SQL、查询计划和日志证据替代。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 在 `Iteration/7-11` 实施真实孤儿查询和同步接续调用。
|
||||
2. 更新审计覆盖基线与功能总结,执行格式化、静态检查和 `go build ./...`。
|
||||
3. 用只读 SQL/查询计划验证候选公平性,用运行日志和数据库事实核验同步接续及卡观测 Outbox。
|
||||
4. 形成七月分支专属 Lore commit;该提交不面向 `main` 移植。
|
||||
5. 发布七月迭代时观察至少两个轮询周期,确认真实孤儿收敛且卡观测 Outbox 正常投递。
|
||||
|
||||
### 回滚
|
||||
|
||||
- 无数据库迁移,回滚七月专属修复提交并重新部署 Worker。
|
||||
- 已正确激活的套餐保持业务事实,不执行反向 SQL。
|
||||
- 回滚后出现孤儿时,使用带状态条件的单卡修复 SQL逐条处理。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。本 Change 明确仅属于七月迭代分支。
|
||||
@@ -0,0 +1,36 @@
|
||||
## 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、查询计划和日志核验。
|
||||
@@ -0,0 +1,99 @@
|
||||
## 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.requested` Outbox
|
||||
- **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** 后续扫描仍可重新判断
|
||||
29
openspec/changes/fix-package-activation-starvation/tasks.md
Normal file
29
openspec/changes/fix-package-activation-starvation/tasks.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## 0. 七月分支基线与边界
|
||||
|
||||
- [ ] 0.1 在 `Iteration/7-11` 记录套餐激活相关代码、现有卡观测 Outbox 装配和工作树状态,确认本 Change 只覆盖“旧套餐过期后接续队首套餐及孤儿恢复”,不涉及 `main`、线上纯 Asynq 热修或其他套餐用例。验证:相关文件差异摘要不包含跨分支移植计划。
|
||||
- [ ] 0.2 保存生产故障只读基线 `100/100/0`,准备真实孤儿候选 SQL和 `EXPLAIN (ANALYZE, BUFFERS)`;所有 SQL 仅允许读取。验证:SQL 能区分占位记录、真实孤儿和每个载体的稳定队首。
|
||||
- [ ] 0.3 增量更新 `.scratch/tech-global-audit/审计覆盖基线.md`:Audit Event N/A、Domain Ledger N/A、Integration Log N/A,Outbox 明确复用 `card.observation.series.requested` 且不新增事件类型。验证:四类公共能力均有明确决定。
|
||||
|
||||
## 1. 真实孤儿公平恢复切片
|
||||
|
||||
- [ ] 1.1 修改 `findAndActivateOrphanPackages`:通过 GORM 执行 PostgreSQL CTE/窗口查询,先按卡或设备选择 `priority ASC, created_at ASC, id ASC` 的唯一队首,再用 `NOT EXISTS` 排除 `status IN (1,2)` 的占位载体,最后限制 100 个真实孤儿;删除逐条 `Count` 和 Go map 二次分组。验证:只读 SQL 在前 100 条全部被占位时仍返回窗口后的真实孤儿,同一载体只返回一条队首。
|
||||
- [ ] 1.2 对每个真实孤儿直接调用 `ActivateNextPendingMainPackage`,根据 `activated bool` 和错误记录实际结果,不调用 `enqueueActivationTask`。验证:孤儿路径不存在 `package:queue:activation` 投递,锁冲突和条件未满足不记录成功。
|
||||
- [ ] 1.3 执行候选 SQL和 `EXPLAIN (ANALYZE, BUFFERS)`,记录真实孤儿数量、执行耗时和扫描行数;没有性能证据时不新增索引。验证:候选查询无逐载体 N+1,单轮最多返回 100 个载体。
|
||||
|
||||
## 2. 过期接续与 Outbox 一致性切片
|
||||
|
||||
- [ ] 2.1 修改 `processExpiredPackage`:事务内只提交旧主套餐过期和关联加油包失效,事务成功后直接调用 `ActivateNextPendingMainPackage`;删除该路径对 `activateNextPackage/enqueueActivationTask` 的调用。验证:旧状态未提交时不会执行新套餐激活,提交后进程退出可由孤儿扫描恢复。
|
||||
- [ ] 2.2 核对同步接续成功事务继续调用 `appendActivationObservation`,并由现有 Writer 原子追加 `card.observation.series.requested`;不得新增套餐激活 Outbox 事件或消费者。验证:激活状态和卡观测事件同事务成功或回滚,稳定事件 ID仍基于 package usage ID。
|
||||
- [ ] 2.3 收紧轮询日志:仅 `activated=true` 记录本轮成功;锁冲突、无队首、占位变化、等待实名和幂等分别记录明确结果。验证:日志包含载体类型、载体 ID、套餐使用记录或可定位的候选信息和触发来源。
|
||||
|
||||
## 3. 七月分支验证与文档
|
||||
|
||||
- [ ] 3.1 对受影响 Go 文件执行 `gofmt`、现有静态检查和 `go build ./...`;按用户要求不新增、修改或运行自动化测试。验证:全量构建退出码为 0,无临时调试标记。
|
||||
- [ ] 3.2 使用只读数据库事实核验:真实孤儿进入候选、同步接续后队首变为 `status=1`、同一载体无第二条生效主套餐、对应卡观测 Outbox 与激活事务一致。验证:保存 SQL、预期和实际结果,不执行批量修复。
|
||||
- [ ] 3.3 新建 `docs/feature-505-package-activation-recovery/功能总结.md` 并更新 README 功能索引,记录七月专属架构、根因、SQL、Outbox 一致性、发布观察、回滚和未执行自动化测试声明。验证:文档不出现 `main`、cherry-pick 或纯 Asynq 热修步骤。
|
||||
- [ ] 3.4 复核七月分支差异并使用中文 Lore commit 提交修复。验证:提交不包含线上 `main` 提案或实现,可独立 revert,`openspec validate fix-package-activation-starvation --strict` 通过。
|
||||
|
||||
## 4. 七月迭代发布观察
|
||||
|
||||
- [ ] 4.1 部署前记录真实孤儿数量、最老等待时间、同载体重复生效检查和卡观测 Outbox 待投递状态;部署后观察至少两个轮询周期。验证:真实孤儿持续收敛、无重复生效、Outbox 正常投递。
|
||||
- [ ] 4.2 准备回滚说明:无数据库迁移,仅 revert 七月专属修复提交并重新部署 Worker;已正确激活的套餐不反向修改。验证:说明包含观察停止条件和带状态保护的单卡人工恢复边界。
|
||||
Reference in New Issue
Block a user