每日清理

This commit is contained in:
2026-08-20 12:03:17 +08:00
parent 143df60485
commit 22b95db2f9
23 changed files with 614 additions and 581 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-20

View File

@@ -0,0 +1,68 @@
## Context
现有归档已按日生成 `tb_log_archive_run`,但 Integration Log 仅在月初终结整月 revision物理删除任务也只接受完整自然月。`tb_log_archive_run` 已按来源和归档日保存对象键、manifest、哈希、统计数和 `cleanup_started_at` / `cleaned_at`,可作为按日留存的断点账本。审计调查的在线边界当前通过每个来源的最大已清理范围计算;若出现清理日期空洞会错误隐藏仍在线的数据。
## Goals / Non-Goals
**Goals:**
- 每天将昨天及更早连续积压的归档日推进到可恢复、可校验、已物理清理的状态。
- 在任一日期归档、终结、对象校验或删除失败时保留在线数据,并使后续运行可安全续跑。
- 保持归档对象和 manifest 不删除,保持现有配置开关作为物理删除总闸门。
- 让在线查询边界只反映连续完成的日期。
**Non-Goals:**
- 不降低卡观测、轮询、Gateway 或 Integration Log 的写入频率。
- 不直接对生产表执行 SQL 删除,不绕过归档验证清理历史数据。
- 不改变归档 JSONL 格式、对象存储提供商或调查 API 路由。
## Decisions
### 以“归档日对”为最小留存单元
日留存以同一 Asia/Shanghai 日期的 Audit 与 Integration 两条归档账本为一个逻辑单元。任务处理上限固定为昨天,先确保两类归档存在;随后为 Integration 生成最终 revision确认该日不存在 `pending` 记录,并复用现有 manifest、对象 metadata、哈希和在线计数复核。
只有两个来源均通过验证后,才开始删除该日在线数据,顺序为 Audit Resource、Audit Event、Integration Log。删除仍按既有 1000 行批次进行;每个来源独立落 `cleanup_started_at``cleaned_at`,从而在进程中断后依据剩余计数续跑。
不采用“定时归档完成即直接 DELETE”的方案对象上传成功不等于可恢复且 Integration Log 的创建日可能仍有待完成记录。
### 按日期从早到晚推进,不跨越失败日
任务枚举昨天及更早仍未完成的归档日,从最早日期开始。某日失败、缺少归档或存在 pending Integration Log 时停止,不处理更晚日期。这样在线数据的已归档边界永远是连续前缀,历史积压在补齐归档后会由同一任务自动追赶。
不采用并行或跳过失败日期的方案:虽然可更快释放部分容量,但会产生在线数据日期空洞;当前调查响应只表达单一 `archived_before` 边界,不能正确描述空洞。
### 将 Integration 最终版由月度改为逐日形成
保留每日普通归档任务作为预归档;日留存任务在准备删除某日数据时对该日期执行最终归档。最终归档仍要求该日没有 pending Integration Log否则不删除并等待下次运行。原月度最终复核和月度留存调度移除避免与日留存争夺同一账本和产生不同的清理范围。
不采用固定延迟天数用户要求昨天及更早数据在可恢复后尽快清理pending 校验是每日期间的实际安全门禁。
### 查询边界按连续清理前缀计算
留存边界查询不再取任意来源的 `MAX(range_end)`。它必须从归档账本确认连续已清理日期,且多来源时间线使用两来源共同完成的连续边界;遇到未清理日期时停止。这样部分删除、失败重试或历史补档都不会把仍在线的旧日期标记为已归档。
### 使用独立文件记录留存结果与失败
新增独立的 Lumberjack/Zap 留存日志配置,默认路径为 Worker 工作目录下的 `logs/audit-retention.log`。日留存和日归档只向该日志写入日期推进、成功数量与耗时,或带日期、来源、失败分类的安全错误摘要;运营者无需在高噪声 `app.log` 中筛选。
不把诊断详情写入 `tb_log_archive_run.error_summary`。账本仍只保存归档状态、对象引用和清理断点;归档或留存失败的具体错误以独立文件为准。保留日志轮转和压缩,避免长期错误积压占满 Worker 磁盘。
不采用为每个日期新建单独文件的方案:一个按日轮转的专用流已经能按日期字段筛选,并避免文件数量随积压日期增长。
## Risks / Trade-offs
- [某日大量 pending 长期不终结,阻塞后续日期清理] → 记录日期、数量和原因;人工修复业务状态后由日任务续跑,不删除未完成数据。
- [单日数百万记录清理超过任务超时] → 维持小批次和来源级断点,任务重试时基于剩余记录续跑;按实测调整任务超时,不增大单事务范围。
- [归档或删除期间进程重启] → 账本的运行租约和清理标记保证归档可重做、删除可继续,且每次删除前重新验证剩余范围。
- [历史日期未建归档账本] → 日任务在最早缺失日期停止;先通过受控补归档形成完整连续账本,再允许自动物理清理。
- [日清理后数据库文件空间不立即下降] → 删除仅回收可复用空间;由维护者根据 PostgreSQL 运行状态决定后续 VACUUM 策略,不在 Worker 内执行高风险表重写。
- [独立留存日志写满磁盘或轮转配置错误] → 使用已有 Lumberjack 轮转、压缩和目录初始化机制;上线前核对 `logs/audit-retention.log` 的生成和轮转。
## Migration Plan
1. 发布包含日归档最终版、日留存和连续边界计算的 Worker/API 二进制,保持物理清理开关关闭。
2. 维护者核对历史归档账本的连续性按日期补齐缺失归档并以只读演练确认对象、manifest 和在线计数。
3. 维护者在低峰期启用 `JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=true` 并重启调度 Worker观察 `/opt/junhong_cmp/worker/logs/audit-retention.log`、账本断点和表大小。
4. 出现异常时关闭开关并重启调度 Worker已删除数据通过已验证的对象存储归档恢复未删除日期保留在线数据。

View File

@@ -0,0 +1,28 @@
## Why
`tb_audit_event``tb_audit_event_resource``tb_integration_log` 每日持续写入百万级记录;现有物理留存任务仅在月初清理上一个完整自然月,在线表、索引和备份在月内持续膨胀。生产需要在确认对象存储归档完整且可恢复后,按日清理在线日志数据。
## What Changes
- 将统一审计事件、审计资源快照和 Integration Log 的物理留存单位由完整自然月改为完整自然日。
- 每次日留存任务处理昨天及更早仍未完成清理的归档日;仅在对应 Audit 与 Integration 归档均成功、完整、可读取且满足最终版要求时,分批物理删除在线表中该日的数据。
- 将清理断点和查询留存边界改为按归档日表达,支持失败后从未完成日期续跑,且不删除未通过校验的日期。
- 保留对象存储归档和现有物理清理开关;不降低卡观测、轮询或外部交互日志的写入频率。
## Capabilities
### New Capabilities
无。
### Modified Capabilities
- `operations-audit`: 审计在线数据在逐日归档校验通过后按归档日物理留存,并向调查查询提供逐日清理边界。
- `external-integration`: 外部交互日志在逐日最终归档校验通过后按归档日物理留存。
## Impact
- Worker 定时任务、审计归档服务、月度留存处理器和留存边界查询。
- `tb_log_archive_run` 的清理断点语义,以及 `tb_audit_event``tb_audit_event_resource``tb_integration_log` 的物理删除范围。
- 对象存储归档读取、manifest 与哈希校验。
- 生产需更新 Worker 二进制、`JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED` 及独立留存日志配置;日志写入 Worker 工作目录下的 `logs/audit-retention.log`,不新增外部 API不直接执行生产数据删除或迁移。

View File

@@ -0,0 +1,19 @@
## ADDED Requirements
### Requirement: 外部交互日志逐日物理留存
系统 SHALL 以 Asia/Shanghai 已结束的自然日为单位物理留存 `tb_integration_log`。删除某日在线外部交互日志前,系统 MUST 为该日生成最终归档版本,确认不存在待完成记录,并验证归档对象、清单、日期范围、记录数量和校验摘要可作为恢复凭证。每次执行 SHALL 从最早尚未完成清理的归档日连续处理至昨天;任一日期不能形成或验证最终归档时,系统 MUST 不删除该日及更晚日期的在线外部交互日志。
#### Scenario: 最终归档通过后清理在线外部交互日志
- **GIVEN** 某已结束自然日的外部交互日志均已进入终态,且该日最终归档对象和清单校验成功
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日的在线外部交互日志并将该归档日标记为已清理,归档对象保持可用
#### Scenario: 存在待完成外部交互日志
- **GIVEN** 某归档日仍存在待完成的外部交互日志
- **WHEN** 日留存任务尝试处理该日期
- **THEN** 系统不删除该日及更晚日期的在线外部交互日志,并在独立日留存日志中记录该日期未形成最终归档的原因
#### Scenario: 历史积压按日期连续补清
- **GIVEN** 存在多个昨天及更早日期尚未完成物理清理
- **WHEN** 日留存任务执行且这些日期依次通过最终归档和完整性校验
- **THEN** 系统按日期从早到晚清理全部连续合格日期

View File

@@ -0,0 +1,41 @@
## ADDED Requirements
### Requirement: 审计在线数据逐日物理留存
系统 SHALL 以 Asia/Shanghai 已结束的自然日为单位处理统一审计事件及其资源快照的在线留存。每次留存执行 SHALL 从最早尚未完成清理的归档日开始,连续处理至昨天;任一归档日未通过完整性校验时,系统 MUST 保留该日及其后续日期的在线审计数据,且不得将它们标记为已清理。系统 MUST 先删除该日的审计资源快照,再删除该日的审计事件,并保留对象存储归档对象及清单作为恢复凭证。
#### Scenario: 已验证日期完成物理清理
- **GIVEN** 某已结束自然日的审计归档成功,归档对象和清单可读取且与在线记录范围、数量和校验摘要一致
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日在线审计资源快照和审计事件,并将该归档日标记为已清理
#### Scenario: 归档校验失败阻断连续清理
- **GIVEN** 某尚未清理的归档日缺少归档、归档校验失败或清单与在线数据不一致
- **WHEN** 日留存任务处理该日期
- **THEN** 系统不删除该日或更晚日期的在线审计数据,并将失败信息写入独立日留存日志
#### Scenario: 失败后从未完成日期续跑
- **GIVEN** 日留存任务在某个归档日失败,较早日期已经标记为已清理
- **WHEN** 后续日留存任务再次执行
- **THEN** 系统从最早未完成清理的归档日继续处理,不重复删除已完成日期的数据
### Requirement: 日留存独立运行日志
系统 SHALL 将日留存的日期推进、清理成功和阻断失败写入独立轮转日志。该日志 MUST 位于 Worker 工作目录下配置的 `logs/audit-retention.log`,每条失败记录 MUST 包含归档日期、数据来源、失败分类和安全错误摘要。日留存和日归档路径 MUST NOT 将失败详情写入 `tb_log_archive_run.error_summary`
#### Scenario: 留存日期被阻断
- **GIVEN** 日留存处理某个归档日时发生缺失账本、pending 记录、归档校验或删除错误
- **WHEN** 系统停止该日期推进
- **THEN** `logs/audit-retention.log` 记录该日期、来源、失败分类和错误摘要
- **AND** 对应账本记录的 `error_summary` 不写入该失败详情
#### Scenario: 留存日期清理成功
- **GIVEN** 某归档日通过全部校验并完成在线数据物理删除
- **WHEN** 系统完成该日期处理
- **THEN** `logs/audit-retention.log` 记录归档日期、各来源清理数量和耗时
### Requirement: 审计调查留存边界连续
系统 SHALL 仅将连续完成物理清理的审计归档日期间公开为已归档边界。在线审计调查接口 MUST 继续允许查询任何尚未物理清理的较早日期,且不得因某个较晚日期已归档而将仍在线的日期错误标记为已归档。
#### Scenario: 清理链存在未完成日期
- **GIVEN** 某较早归档日尚未完成清理
- **WHEN** 后续日期的归档已成功生成
- **THEN** 审计调查接口仍将较早未清理日期视为在线可查询数据

View File

@@ -0,0 +1,19 @@
## 1. 按日归档与留存编排
- [x] 1.1 将 Integration Log 最终归档能力收敛为指定已结束自然日;保留 pending 记录时不产生最终 revision并通过独立留存日志输出原因而不更新 `error_summary`
- [x] 1.2 将现有月度留存服务改为逐日处理:枚举昨天及更早未完成日期,按日期从早到晚确保双来源归档、最终归档和完整性校验。
- [x] 1.3 复用现有分批删除和来源级清理断点,按 Audit Resource、Audit Event、Integration Log 的顺序物理删除单日在线数据;中断或失败时可从剩余记录续跑。
- [x] 1.4 遇到缺失账本、归档失败、对象或 manifest 校验失败、数量不一致或 pending Integration Log 时停止日期推进;不清理该日及后续日期,且不向 `tb_log_archive_run.error_summary` 写入失败详情。
## 2. Worker 调度与查询边界
- [x] 2.1 为 Worker 新增独立留存日志配置和目录初始化,默认输出 `logs/audit-retention.log`,复用现有轮转与压缩能力,并在退出时刷新日志。
- [x] 2.2 将 Worker 留存调度由月初任务改为每日任务,调整任务类型、唯一窗口、超时、处理器日志及归档注册,避免月度任务与日留存并发处理同一账本。
- [x] 2.3 保留 `JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED` 作为物理删除总闸门;关闭时执行逐日只读校验,不写清理断点、不删除在线数据。
- [x] 2.4 改造审计和 Integration Log 在线查询留存边界,按连续已清理日期计算,并使混合时间线不将仍在线的空洞日期误报为已归档。
- [x] 2.5 更新留存模拟 CLI 或等价可执行验证入口以逐日范围验证归档、最终版、对象、manifest、数据库数量、清理断点及独立日志输出。
## 3. 运行说明与验证
- [x] 3.1 更新生产运行说明,明确日留存启用前的历史归档补齐、只读演练、开关启用、`/opt/junhong_cmp/worker/logs/audit-retention.log` 观察、账本断点、异常停用和归档恢复步骤。
- [x] 3.2 执行 `gofmt``go build ./cmd/api ./cmd/worker``go run cmd/gendocs/main.go``openspec validate daily-audit-log-retention --strict` 与日留存模拟验证;不新增或恢复自动化测试。(隔离库模拟待维护者在具备显式 `JUNHONG_*` 配置的环境执行确认)

View File

@@ -101,6 +101,25 @@
- **WHEN** 富友主扫统一下单返回失败或请求结果未知
- **THEN** 系统返回项目稳定错误且已接入外部交互日志的调用记录脱敏结果
### Requirement: 外部交互日志逐日物理留存
系统 SHALL 以 Asia/Shanghai 已结束的自然日为单位物理留存 `tb_integration_log`。删除某日在线外部交互日志前,系统 MUST 为该日生成最终归档版本,确认不存在待完成记录,并验证归档对象、清单、日期范围、记录数量和校验摘要可作为恢复凭证。每次执行 SHALL 从最早尚未完成清理的归档日连续处理至昨天;任一日期不能形成或验证最终归档时,系统 MUST 不删除该日及更晚日期的在线外部交互日志。
#### Scenario: 最终归档通过后清理在线外部交互日志
- **GIVEN** 某已结束自然日的外部交互日志均已进入终态,且该日最终归档对象和清单校验成功
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日的在线外部交互日志并将该归档日标记为已清理,归档对象保持可用
#### Scenario: 存在待完成外部交互日志
- **GIVEN** 某归档日仍存在待完成的外部交互日志
- **WHEN** 日留存任务尝试处理该日期
- **THEN** 系统不删除该日及更晚日期的在线外部交互日志,并在独立日留存日志中记录该日期未形成最终归档的原因
#### Scenario: 历史积压按日期连续补清
- **GIVEN** 存在多个昨天及更早日期尚未完成物理清理
- **WHEN** 日留存任务执行且这些日期依次通过最终归档和完整性校验
- **THEN** 系统按日期从早到晚清理全部连续合格日期
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。

View File

@@ -46,6 +46,49 @@
- **WHEN** 该操作的审计事件构造、校验或持久化失败
- **THEN** 系统提交或返回该业务操作原本的结果,并以请求关联标识、动作编码和资源标识记录审计失败
### Requirement: 审计在线数据逐日物理留存
系统 SHALL 以 Asia/Shanghai 已结束的自然日为单位处理统一审计事件及其资源快照的在线留存。每次留存执行 SHALL 从最早尚未完成清理的归档日开始,连续处理至昨天;任一归档日未通过完整性校验时,系统 MUST 保留该日及其后续日期的在线审计数据,且不得将它们标记为已清理。系统 MUST 先删除该日的审计资源快照,再删除该日的审计事件,并保留对象存储归档对象及清单作为恢复凭证。
#### Scenario: 已验证日期完成物理清理
- **GIVEN** 某已结束自然日的审计归档成功,归档对象和清单可读取且与在线记录范围、数量和校验摘要一致
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日在线审计资源快照和审计事件,并将该归档日标记为已清理
#### Scenario: 归档校验失败阻断连续清理
- **GIVEN** 某尚未清理的归档日缺少归档、归档校验失败或清单与在线数据不一致
- **WHEN** 日留存任务处理该日期
- **THEN** 系统不删除该日或更晚日期的在线审计数据,并将失败信息写入独立日留存日志
#### Scenario: 失败后从未完成日期续跑
- **GIVEN** 日留存任务在某个归档日失败,较早日期已经标记为已清理
- **WHEN** 后续日留存任务再次执行
- **THEN** 系统从最早未完成清理的归档日继续处理,不重复删除已完成日期的数据
### Requirement: 日留存独立运行日志
系统 SHALL 将日留存的日期推进、清理成功和阻断失败写入独立轮转日志。该日志 MUST 位于 Worker 工作目录下配置的 `logs/audit-retention.log`,每条失败记录 MUST 包含归档日期、数据来源、失败分类和安全错误摘要。日留存和日归档路径 MUST NOT 将失败详情写入 `tb_log_archive_run.error_summary`
#### Scenario: 留存日期被阻断
- **GIVEN** 日留存处理某个归档日时发生缺失账本、pending 记录、归档校验或删除错误
- **WHEN** 系统停止该日期推进
- **THEN** `logs/audit-retention.log` 记录该日期、来源、失败分类和错误摘要
- **AND** 对应账本记录的 `error_summary` 不写入该失败详情
#### Scenario: 留存日期清理成功
- **GIVEN** 某归档日通过全部校验并完成在线数据物理删除
- **WHEN** 系统完成该日期处理
- **THEN** `logs/audit-retention.log` 记录归档日期、各来源清理数量和耗时
### Requirement: 审计调查留存边界连续
系统 SHALL 仅将连续完成物理清理的审计归档日期间公开为已归档边界。在线审计调查接口 MUST 继续允许查询任何尚未物理清理的较早日期,且不得因某个较晚日期已归档而将仍在线的日期错误标记为已归档。
#### Scenario: 清理链存在未完成日期
- **GIVEN** 某较早归档日尚未完成清理
- **WHEN** 后续日期的归档已成功生成
- **THEN** 审计调查接口仍将较早未清理日期视为在线可查询数据
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。