From c7c2b17d78737ae943378068e88c49db8c34d2ef Mon Sep 17 00:00:00 2001 From: break Date: Mon, 7 Sep 2026 11:34:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=92=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/operations-audit/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/package-queue-activation/spec.md | 0 .../tasks.md | 0 openspec/specs/operations-audit/spec.md | 10 ++- .../specs/package-queue-activation/spec.md | 81 +++++++++++++++++++ 12 files changed, 90 insertions(+), 1 deletion(-) rename openspec/changes/{fix-audit-retention-null-boundary => archive/2026-09-07-fix-audit-retention-null-boundary}/.openspec.yaml (100%) rename openspec/changes/{fix-audit-retention-null-boundary => archive/2026-09-07-fix-audit-retention-null-boundary}/design.md (100%) rename openspec/changes/{fix-audit-retention-null-boundary => archive/2026-09-07-fix-audit-retention-null-boundary}/proposal.md (100%) rename openspec/changes/{fix-audit-retention-null-boundary => archive/2026-09-07-fix-audit-retention-null-boundary}/specs/operations-audit/spec.md (100%) rename openspec/changes/{fix-audit-retention-null-boundary => archive/2026-09-07-fix-audit-retention-null-boundary}/tasks.md (100%) rename openspec/changes/{fix-main-package-activation-starvation => archive/2026-09-07-fix-main-package-activation-starvation}/.openspec.yaml (100%) rename openspec/changes/{fix-main-package-activation-starvation => archive/2026-09-07-fix-main-package-activation-starvation}/design.md (100%) rename openspec/changes/{fix-main-package-activation-starvation => archive/2026-09-07-fix-main-package-activation-starvation}/proposal.md (100%) rename openspec/changes/{fix-main-package-activation-starvation => archive/2026-09-07-fix-main-package-activation-starvation}/specs/package-queue-activation/spec.md (100%) rename openspec/changes/{fix-main-package-activation-starvation => archive/2026-09-07-fix-main-package-activation-starvation}/tasks.md (100%) create mode 100644 openspec/specs/package-queue-activation/spec.md diff --git a/openspec/changes/fix-audit-retention-null-boundary/.openspec.yaml b/openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/.openspec.yaml similarity index 100% rename from openspec/changes/fix-audit-retention-null-boundary/.openspec.yaml rename to openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/.openspec.yaml diff --git a/openspec/changes/fix-audit-retention-null-boundary/design.md b/openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/design.md similarity index 100% rename from openspec/changes/fix-audit-retention-null-boundary/design.md rename to openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/design.md diff --git a/openspec/changes/fix-audit-retention-null-boundary/proposal.md b/openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/proposal.md similarity index 100% rename from openspec/changes/fix-audit-retention-null-boundary/proposal.md rename to openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/proposal.md diff --git a/openspec/changes/fix-audit-retention-null-boundary/specs/operations-audit/spec.md b/openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/specs/operations-audit/spec.md similarity index 100% rename from openspec/changes/fix-audit-retention-null-boundary/specs/operations-audit/spec.md rename to openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/specs/operations-audit/spec.md diff --git a/openspec/changes/fix-audit-retention-null-boundary/tasks.md b/openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/tasks.md similarity index 100% rename from openspec/changes/fix-audit-retention-null-boundary/tasks.md rename to openspec/changes/archive/2026-09-07-fix-audit-retention-null-boundary/tasks.md diff --git a/openspec/changes/fix-main-package-activation-starvation/.openspec.yaml b/openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/.openspec.yaml similarity index 100% rename from openspec/changes/fix-main-package-activation-starvation/.openspec.yaml rename to openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/.openspec.yaml diff --git a/openspec/changes/fix-main-package-activation-starvation/design.md b/openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/design.md similarity index 100% rename from openspec/changes/fix-main-package-activation-starvation/design.md rename to openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/design.md diff --git a/openspec/changes/fix-main-package-activation-starvation/proposal.md b/openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/proposal.md similarity index 100% rename from openspec/changes/fix-main-package-activation-starvation/proposal.md rename to openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/proposal.md diff --git a/openspec/changes/fix-main-package-activation-starvation/specs/package-queue-activation/spec.md b/openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/specs/package-queue-activation/spec.md similarity index 100% rename from openspec/changes/fix-main-package-activation-starvation/specs/package-queue-activation/spec.md rename to openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/specs/package-queue-activation/spec.md diff --git a/openspec/changes/fix-main-package-activation-starvation/tasks.md b/openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/tasks.md similarity index 100% rename from openspec/changes/fix-main-package-activation-starvation/tasks.md rename to openspec/changes/archive/2026-09-07-fix-main-package-activation-starvation/tasks.md diff --git a/openspec/specs/operations-audit/spec.md b/openspec/specs/operations-audit/spec.md index ae40ebb..ece86e0 100644 --- a/openspec/specs/operations-audit/spec.md +++ b/openspec/specs/operations-audit/spec.md @@ -8,7 +8,7 @@ ### Requirement: 审计时间线 -系统 SHALL 支持按事件、操作者、资源、请求、关联标识和资金维度查询已记录的审计事实。新建的 `tb_integration_log` 在已取得稳定审计事件时 SHALL 写入其内部 ID 作为 `audit_event_id`,并且该日志的非空 `integration_id` SHALL 出现在对应事件列表和事件详情的 `investigation_refs.integration_refs` 中;关联生成失败时系统 MUST 保留 Integration Log 并记录可排查告警,且 MUST NOT 按名称、时间或摘要推断关联。历史 Integration Log 不在本要求的回填范围内。审计事件的构造、校验或持久化失败 MUST 记录可关联的结构化错误日志和二次失败记录,且 MUST NOT 改变已通过业务校验的业务操作结果或接口响应。 +系统 SHALL 支持按事件、操作者、资源、请求、关联标识和资金维度查询已记录的审计事实。新建的 `tb_integration_log` 在已取得稳定审计事件时 SHALL 写入其内部 ID 作为 `audit_event_id`,并且该日志的非空 `integration_id` SHALL 出现在对应事件列表和事件详情的 `investigation_refs.integration_refs` 中;关联生成失败时系统 MUST 保留 Integration Log 并记录可排查告警,且 MUST NOT 按名称、时间或摘要推断关联。历史 Integration Log 不在本要求的回填范围内。审计事件的构造、校验或持久化失败 MUST 记录可关联的结构化错误日志和二次失败记录,且 MUST NOT 改变已通过业务校验的业务操作结果或接口响应。尚无完成物理清理记录时,审计调查接口 MUST 继续查询在线审计事实,且返回的留存边界不得将数据标记为已归档。 #### Scenario: 审计时间线 @@ -46,6 +46,14 @@ - **WHEN** 该操作的审计事件构造、校验或持久化失败 - **THEN** 系统提交或返回该业务操作原本的结果,并以请求关联标识、动作编码和资源标识记录审计失败 +#### Scenario: 尚无物理清理记录时查询在线审计 + +- **GIVEN** 审计或外部交互日志尚无完成物理清理的归档运行记录 +- **WHEN** 调用任一依赖留存边界的审计调查接口 +- **THEN** 系统返回当前在线数据或空列表 +- **AND** 响应中的 `archived_before` 为空 +- **AND** 系统不得因空留存边界返回服务端错误 + ### Requirement: 轮询审计事实保留边界 系统 SHALL 仅为产生业务事实变化、轮询失败、结果未知或人工触发的轮询操作持久化统一审计事件及资源快照。成功且无业务变化的自动轮询 MUST 不创建 `tb_audit_event` 或 `tb_audit_event_resource`,审计调查接口仅返回实际已保留的历史事实。 diff --git a/openspec/specs/package-queue-activation/spec.md b/openspec/specs/package-queue-activation/spec.md new file mode 100644 index 0000000..75a7aa0 --- /dev/null +++ b/openspec/specs/package-queue-activation/spec.md @@ -0,0 +1,81 @@ +# 套餐队列激活当前行为 + +## Purpose + +描述主套餐到期后的队列接续、无占位主套餐时的孤儿恢复,以及异步激活结果和锁冲突的重试语义,确保待生效套餐不因候选窗口或事务可见性而长期无法激活。 + +## Requirements + +### Requirement: 当前主套餐过期后自动激活下一个 + +系统 SHALL 在生效中或已用完主套餐到期时,先提交旧主套餐及关联加油包的状态事务,再投递同一载体队首待生效套餐的现有 Asynq 激活任务;系统 MUST NOT 在旧套餐事务提交前投递任务。 + +#### Scenario: 事务提交后投递队首套餐 + +- **WHEN** 轮询处理一个到期的 `status=1` 或 `status=2` 主套餐,且存在队首待生效套餐 +- **THEN** 系统先提交旧主套餐 `status=3` 和关联加油包 `status=4` 的事务 +- **AND** 提交成功后查询稳定队首并投递 `package:queue:activation` +- **AND** 消费者读取时旧主套餐不再处于 `status IN (1,2)` + +#### Scenario: 过期事务失败 + +- **WHEN** 更新旧主套餐或关联加油包失败 +- **THEN** 事务回滚 +- **AND** 系统不得投递下一套餐激活任务 +- **AND** 下一轮过期扫描仍可重新处理 + +#### Scenario: 提交后入队失败 + +- **WHEN** 旧套餐事务已经提交,但 Asynq 入队失败或进程退出 +- **THEN** 队首套餐保持 `status=0` +- **AND** 周期孤儿扫描重新发现并补投该套餐 + +#### Scenario: 无待生效套餐 + +- **WHEN** 旧套餐过期提交后不存在有效队首待生效套餐 +- **THEN** 系统不投递激活任务 +- **AND** 沿用既有无套餐停机检查 + +### Requirement: 孤儿待生效套餐必须公平恢复 + +系统 SHALL 在数据库中先选择每个卡或设备载体唯一的队首待生效主套餐,并排除仍有 `status IN (1,2)` 占位主套餐的载体,最后才执行单轮 100 个真实孤儿上限。队首顺序 MUST 为 `priority ASC, created_at ASC, id ASC`。 + +#### Scenario: 固定窗口全部为非孤儿 + +- **WHEN** 排序靠前的 100 条待生效记录均有占位主套餐,窗口之后存在真实孤儿 +- **THEN** 数据库先排除前 100 条非孤儿 +- **AND** 窗口后的真实孤儿进入本轮候选并被投递 + +#### Scenario: 同一载体有多条 pending + +- **WHEN** 一个真实孤儿载体存在多条待生效主套餐 +- **THEN** 本轮只选择 priority 最小、created_at 最早、id 最小的一条 +- **AND** 该载体只占一个恢复名额 + +#### Scenario: 生效中或已用完套餐占位 + +- **WHEN** pending 所属载体存在 `status=1` 或 `status=2` 主套餐 +- **THEN** 该载体不得进入孤儿候选 + +### Requirement: Asynq 激活结果必须准确且可重试 + +系统 SHALL 仅在本次实际把套餐推进为 `status=1` 时记录新激活成功。Redis 激活锁冲突 MUST 返回错误,使 Asynq 按既有 `MaxRetry(3)` 重试,不得确认未执行任务成功。 + +#### Scenario: 锁冲突触发重试 + +- **WHEN** 消费者未取得载体级套餐激活锁 +- **THEN** Service 返回套餐激活冲突错误 +- **AND** Handler 将错误返回 Asynq +- **AND** 本次不记录激活成功 + +#### Scenario: 本次实际激活 + +- **WHEN** 套餐为待生效、载体无占位主套餐且满足激活条件 +- **THEN** Service 返回 `activated=true` +- **AND** Handler 记录包含套餐使用记录和触发类型的成功日志 + +#### Scenario: 条件暂不满足 + +- **WHEN** Service 复检发现占位套餐或等待实名条件 +- **THEN** Service 返回 `activated=false`且不修改套餐 +- **AND** Handler 记录未激活原因,不记录成功