全局审计完成
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s

This commit is contained in:
2026-08-07 11:02:52 +08:00
parent 88cc5e96ec
commit c64f3d8b80
94 changed files with 8641 additions and 6714 deletions

View File

@@ -0,0 +1,166 @@
# 审计归档灰度操作手册
## 灰度开关
灰度期必须保持:
```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 未删除。