Files
junhong_cmp_fiber/docs/feature-504-multi-view-audit-center/归档灰度操作手册.md
break c64f3d8b80
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s
全局审计完成
2026-08-07 11:02:52 +08:00

167 lines
11 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.
# 审计归档灰度操作手册
## 灰度开关
灰度期必须保持:
```bash
JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false
```
该配置使每月任务和人工投递的 `audit:monthly:retention` 只执行只读门禁演练,不写清理断点、不删除在线数据。每日 Audit/Integration 归档和 Integration 月度最终复核不受影响。
完整自然月可以在显式确认的隔离测试数据库中按真实日界构造,无需等待现实时间流逝。测试环境的完整月 dry-run 全部通过后,才可临时启用清理验证删除范围;生产环境仍须由发布决策将开关改为 `true`。关闭开关并重启 Worker 即可回滚;不得手工删除在线日志或对象存储归档。
## 隔离测试环境完整月仿真
仓库提供 `cmd/audit-retention-simulate`,固定使用 `2001-02` 和专属实例标识。命令仅允许数据库名包含 `test`,且确认值必须与当前数据库名完全一致;目标月已有任何 Audit、Integration 或归档账本时立即拒绝,避免误清理已有数据。
```bash
source .env.local
JUNHONG_AUDIT_RETENTION_SIMULATION_CONFIRM="$JUNHONG_DATABASE_DBNAME" \
go run ./cmd/audit-retention-simulate
```
命令为目标月每日构造一条 Audit Event、Event Resource 和 Integration Log并在月初前一秒与下月零点各放置一组边界哨兵。流程依次执行每日归档、对象和 manifest 回读、故障重试、Integration 最终 revision、清理关闭 dry-run、清理断点核对、测试库物理清理及边界复核。Redis 不参与该留存流程。
## 每日观测
以下查询只读取归档运行账本。将日期替换为待验收自然月的半开区间:
```sql
SELECT
archive_date,
source,
status,
attempt_count,
revision,
is_final,
event_count,
resource_count,
record_count,
uncompressed_bytes,
compressed_bytes,
CASE
WHEN uncompressed_bytes = 0 THEN 1
ELSE ROUND(compressed_bytes::numeric / uncompressed_bytes, 4)
END AS compression_ratio,
sha256,
object_key,
manifest_key,
error_summary,
completed_at,
cleanup_started_at,
cleaned_at
FROM tb_log_archive_run
WHERE archive_date >= DATE '2026-07-01'
AND archive_date < DATE '2026-08-01'
AND source IN ('audit', 'integration')
ORDER BY archive_date, source;
```
每日必须记录两类归档是否成功、重试次数、完成时间、对象大小、压缩率、SHA-256、Audit 事件/资源数量、Integration 记录数量及 revision。对象 metadata、manifest 和 gzip 实际 SHA-256/计数差异以归档任务自身复核结果及日志为准,不能只看账本字段。
## 月度汇总与积压
```sql
WITH expected AS (
SELECT day::date AS archive_date, source
FROM generate_series(DATE '2026-07-01', DATE '2026-07-31', INTERVAL '1 day') AS day
CROSS JOIN (VALUES ('audit'), ('integration')) AS sources(source)
)
SELECT
COUNT(*) AS expected_runs,
COUNT(r.id) FILTER (WHERE r.status = 'success') AS successful_runs,
ROUND(COUNT(r.id) FILTER (WHERE r.status = 'success')::numeric / COUNT(*), 4) AS success_rate,
COUNT(*) FILTER (WHERE r.id IS NULL OR r.status <> 'success') AS backlog_runs,
COALESCE(SUM(r.compressed_bytes), 0) AS object_bytes,
COALESCE(SUM(r.uncompressed_bytes), 0) AS source_bytes,
CASE
WHEN COALESCE(SUM(r.uncompressed_bytes), 0) = 0 THEN 1
ELSE ROUND(SUM(r.compressed_bytes)::numeric / SUM(r.uncompressed_bytes), 4)
END AS compression_ratio,
COALESCE(MAX(r.revision), 0) AS max_revision,
COALESCE(SUM(r.attempt_count), 0) AS attempts
FROM expected e
LEFT JOIN tb_log_archive_run r
ON r.archive_date = e.archive_date
AND r.source = e.source
AND r.instance_id = 'primary';
```
Integration 月度最终复核后,目标月所有 `source='integration'` 记录必须同时满足 `status='success'``is_final=true`,且无 `pending` 阻断日志。灰度期开关关闭时,目标月所有 `cleanup_started_at/cleaned_at` 必须为 `NULL`
## 清理 dry-run 与耗时估算
dry-run 只执行现有月度门禁的只读核对:完整月每日 ledger/manifest、对象 metadata、对象大小、SHA-256、Audit 事件与资源计数、Integration 最终 revision。生产和共享灰度环境禁止调用物理清理方法验证 dry-run显式确认的隔离测试数据库必须在 dry-run 通过后执行一次物理清理,用于证明删除范围。
先统计预计删除行数和 1000 行批次数:
```sql
WITH counts AS (
SELECT
(SELECT COUNT(*) FROM tb_audit_event WHERE created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS audit_events,
(SELECT COUNT(*) FROM tb_audit_event_resource r JOIN tb_audit_event e ON e.id = r.audit_event_id WHERE e.created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND e.created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS audit_resources,
(SELECT COUNT(*) FROM tb_integration_log WHERE created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS integration_logs
)
SELECT
*,
CEIL(audit_events / 1000.0) + CEIL(audit_resources / 1000.0) + CEIL(integration_logs / 1000.0) AS estimated_batches
FROM counts;
```
清理预估耗时为 `estimated_batches × 灰度环境单批删除 P95`,并额外预留 30%。未取得真实单批 P95 前不得启用清理。
## 告警与启用门禁
- 每日 08:00 前任一前日 Audit/Integration 记录缺失、失败或仍为 runningcritical。
- running 持续超过 3 小时、`attempt_count > 1` 或对象/manifest 复核失败critical。
- 有数据时压缩率不在 `(0, 1]`、SHA-256 为空或对象大小为零critical。
- 月度最终复核后任一 Integration 记录 `is_final=false` 或存在 pendingcritical。
- 只归档模式出现任一 `cleanup_started_at``cleaned_at`critical立即停 Worker 并调查。
- 完整月成功率必须为 100%积压、hash 差异、计数差异和未终结 Integration 数必须均为 0。
- 故障重试、月度只读 dry-run、启停与关闭回滚均须保留时间、环境、操作者、日志位置和结果证据。
任何一项未通过时保持生产开关关闭,修复后重新执行完整自然月仿真;不得以补写验收记录替代真实归档、对象复核和删除范围核对。
## 全局审计切换监控阈值
contract 切换期按以下阈值观测;任一 critical 条件命中时停止扩大发布范围,保留已提交事实并前向修复。
| 观测项 | 阈值 | 证据来源与处置 |
|---|---|---|
| 关键成功审计写入 | 任一失败即 critical | 业务与 Audit Event 同事务回滚;按 action/request/correlation 定位异常生产者,不得补写成功事件。 |
| 失败/拒绝审计二次写入 | `secondary_write_failure_count` 增量必须为 0 | 监控“失败或拒绝审计二次写入失败” critical 日志;保留原业务错误。 |
| 未注册 action/resource/role | 数量必须为 0 | 运行 `go run ./cmd/audit-coverage`;任一未注册项阻断发布。 |
| 安全凭据落库或响应命中 | 数量必须为 0 | 使用禁止键/凭据值受控抽样;发现后停止相关生产者并前向修复,不修改已提交审计事实。 |
| 批量根子计数差异 | 非法统计、缺少父事件或 correlation 数必须为 0 | 根事件 `success_count/fail_count` 与可查子事件不一致即 critical暂停该批量用例。 |
| 旧 Writer 调用 | 生产写调用必须为 0 | 覆盖门禁只允许旧资产历史查询和手动轮询运行 ledger 白名单;命中 Create/Update/裸 goroutine 立即阻断发布。 |
| 数据库审计查询 | 单条受控查询 < 50ms | 使用在线数据的 `EXPLAIN (ANALYZE, BUFFERS)` 观测;超阈值先检查索引和无界时间范围,不直接增加 Redis 缓存或分区。 |
| 审计读接口 | P95 < 200msP99 < 500ms | 使用现有 Access Log 统计真实流量;本 Change 不新增或运行自动化测试。 |
## contract 发布与回滚
1. 发布前确认数据库迁移版本为 `205``dirty=false`,清理开关保持关闭,覆盖门禁中的旧 Writer 生产调用为 0。
2. 发布异常时先停止扩大流量,暂停明确异常的生产者或 Worker不删除当前在线 Audit Event、Integration Log、Domain Ledger、Outbox 或已校验对象存储归档。
3. 应用回滚只能回到“旧 Writer 已停写”的 contract 基线版本。不得回到恢复 `tb_account_operation_log`/`tb_asset_operation_log` 生产写入或裸 goroutine 审计的版本;无合法基线时只做前向修复。
4. 有任何 Audit Event 或归档 ledger 事实的环境不执行 `000199`/`000205` down有 Outbox parent、长 correlation 或订单预占事实的环境不执行 `000200`/`000201`/`000203` down。业务回滚保留版本 `205` 结构,不以 migration down 代替应用回滚。
5. 需要暂停物理清理时,将 `JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false` 并重启 Worker每日归档和新业务审计继续运行。
6. 已清理月份不从对象存储回填 PostgreSQL不伪造在线历史查询仍按 retention 边界返回已归档错误。错误业务结论通过新的受控业务动作和新 Audit Event 前向修正,不改写原事实。
7. 回滚后重新运行覆盖门禁、路由/OpenAPI 扫描、数据库凭据抽样和归档账本核对;任一旧 Writer 新增、在线事实减少或归档对象丢失都阻断结案。
## 迁移演练约束
- 本 Change 的增量迁移范围为 `000199`~`000205`。共享 `.env.local` 测试库已有 Audit 和归档事实,不允许直接 down。
- down 只能在无事实的隔离 schema/数据库执行;`000200``000203` 会删除业务快照列,不得对有事实环境演练。
- `000199_create_audit_event.down.sql` 自带事务边界,不应依赖外层事务自动清理隔离 schema演练结束必须显式核对并删除精确的隔离对象。
- 2026-08-06 在 `.env.local` 测试 PostgreSQL 中对无事实隔离 schema 完成 `000199`~`000205` up/down上行后 Audit/归档表存在,逆序 down 后表和增量列均恢复;隔离 schema 已显式删除,`public` 保持版本 `205``dirty=false`
## 2026-08-06 仿真记录
- 环境:`.env.local``junhong_cmp_test` 测试数据库及其对象存储。
- 月份:`2001-02`,共 28 个完整自然日;归档账本 56 条,成功 56 条。
- Integration 最终 revision28/28故障重试后最大 revision=2、最大 attempt_count=2。
- 压缩率0.5047dry-run 时目标月 Audit Event/Event Resource/Integration Log 各 28 条,预计 3 个 1000 行删除批次,且清理断点为 0。
- 物理清理后目标月三表均为 0月前一秒和下月零点的 Audit Event、Event Resource、Integration Log 共 6 条全部保留。
- 目标月 56 条账本均具有 `cleanup_started_at``cleaned_at`;当月生成 1 条 `evt_retention_2001_02` 清理审计;对象归档及 manifest 未删除。