# 审计系统文档体检快照（2026-05-03）

> **性质**：对账快照，**非契约文档**
> **契约文档**：[`01-prd.md`](./01-prd.md) / [`03-architecture.md`](./03-architecture.md)
> **触发事件**：用户要求"整体看一下审计系统是不是真的按文档说的实现了"
> **对账方式**：本目录 9 份模块文档 vs `backend/src/core/observability/audit/` 实际代码 + `backend/prisma/schema/platform_audit.prisma`
> **执行人**：Claude（Opus 4.7, 1M 上下文）
> **下游动作**：见末尾「修复 PR 拆分」与「执行建议」

---

## 一句话结论

**9 份文档 6096 行里，主要问题集中在 v1.1.0 (2025-12-19) changelog 提前宣布完成的 4 项功能**——数据库触发器、HMAC 强约束、实时告警通知、SOX/GDPR 报表完整性。其中 **数据库触发器** 这一项在 PRD / Architecture / Roadmap / Changelog / Configuration / 验收标准 **6 个地方** 都写了 ✅，但代码中**完全不存在**——这是文档失真的最大震中。其它都是路径漂移、数字过期、示例代码与实现偏离。

代码本身约 4250 行 TypeScript，核心写入路径真实、装饰器机制扎实；但有 1 条隐藏的合规级 race condition、1 条硬编码默认 HMAC 密钥、3 件被宣传却根本没接的功能。离 README 自评的 "🟢 生产就绪 / SOX 合规" 还差几个 PR 的距离。

---

## A. 实现核对（声明 vs 代码）

### ✅ 真的做了
| 声明 | 证据 |
|---|---|
| `@Auditable / @Sensitive / @Financial` 装饰器自动拦截写库 | `audit-log.interceptor.ts` 通过 `APP_INTERCEPTOR` 全局挂载（`app.module.ts:130`），实测 288/174/5 处调用 |
| 5W1H 字段 + 28 字段 schema | `platform_audit.prisma:1-80`，who/what/when/where/why/how 齐全 |
| SHA-256 哈希链 | `hash-chain.service.ts:30-47` + `audit.service.ts:135/177/189` 写 previousHash + currentHash |
| HMAC 数字签名 | `hash-chain.service.ts:102-121` 用 `createHmac('sha256')`，`audit.service.ts:180` 写 `signature` 字段 |
| 完整性定时任务 | `integrity.service.ts` 有 `@Cron('0 2 * * *')` daily / `@Cron('0 3 * * 0')` weekly |
| 多租户/多区域字段 | `region` + `tenantId` 必填，索引齐全（`@@index([region, tenantId, when])`） |
| SOX 报表 | `compliance-report.service.ts:781` 行 |
| **GDPR 数据访问报表** | `compliance-report.service.ts:585-658` `generateGdprDataAccessReport()` 真实存在 |
| 20+ API 接口 | `audit.controller.ts:664` 行，~20 端点 |

### ⚠️ 部分做了
| 声明 | 实情 |
|---|---|
| 集成覆盖率"44 模块 / 234 操作 / 83% controller" | 实际 **288 个 `@Auditable`**（数字过期）；**41/96 controller 完全未集成**（覆盖率 57%，不是 83%） |
| 实时告警系统 | `AlertService` 643 行，**通知发送是注释掉的 stub**：`alert.service.ts:187/374/522` 三处 `TODO: 集成通知系统/发送实时通知/发送邮件汇总` + `:193,523` 真正的发送代码 `// await this.notificationService.send(...)` 整行注释。检测能跑、通知不发 |
| 测试覆盖 | 只有 `testing/backend/integration/iam-admin/iam-audit.api.test.ts` 一个集成测试 + 一份 shell 脚本；审计**自身**（哈希链 / HMAC / 完整性）零单测 |

### ❌ 文档说了但代码里没有
| 声明 | 实情 |
|---|---|
| **PostgreSQL 触发器禁止修改/删除** | 全仓 `grep CREATE TRIGGER \| CREATE RULE \| REVOKE` = **零结果**。`audit_log` 在 DB 层依然可被 UPDATE/DELETE |
| `audit_database_change_log` 表 | 表建了，**全仓零写入**（grep `auditDatabaseChangeLog` 主代码无引用）—— dead table |
| `audit_sensitive_operation_log` 表 | 同上，**零写入** —— dead table |

→ 4 张表里 2 张是装饰品。

### 🔴 隐藏 bug（代码层，不是文档问题）
| 问题 | 证据 | 影响 |
|---|---|---|
| **HMAC 密钥默认值硬编码** | `hash-chain.service.ts:17-21` 拿不到 `AUDIT_HMAC_SECRET` 时回落到字面量 `'default-audit-hmac-secret-change-in-production-2025'`，仅 `logger.warn` | 任何拿到源码的人都能伪造签名 |
| **哈希链 race condition** | `audit.service.ts:135` 读 previousHash 与 `:185` create 之间无事务/锁，同租户并发请求会两条同时基于同一个 previousHash 建链 | `currentHash @unique` 救不了（两条 currentHash 不同），事后 `verifyHashChain` 会判失败。这是真实的合规级 bug |
| **审计写入 fire-and-forget 无重试** | `interceptor.ts:142-153` 是 `tap(() => auditService.log().then().catch())`，写失败只 `logger.error` | DB 短暂不可用 = 静默丢日志 |
| **`extractEntityId` 兜底生成临时 UUID** | `interceptor.ts:291` 没匹配到 ID 时 `return uuidv4()` | 每条列表/创建请求都有一个**假 entityId** 写进表，污染索引和按实体查询的能力 |
| **`isFinancialOperation` URL 关键词匹配会误标** | `interceptor.ts:294-309` 看到 `billing/expense/transaction` 就标 `isFinancial=true` → 触发 7 年保留 | GET 列表也会中招 → 数据膨胀 |

---

## B. 文档失实点清单（按文档分）

### 📕 README.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| R1 | L64 | `backend/src/audit/` | `backend/src/core/observability/audit/` |
| R2 | L42-47 | "10 大特性"包含"数据库防篡改 PostgreSQL 触发器" | 触发器不存在 |
| R3 | L55 | 集成覆盖率"44 模块 / 234 操作 / 181 敏感操作 / 83%" | 实测 288 / 174 / 5 / 57% |
| R4 | L185 | "实时告警系统：高风险操作自动检测和通知" | 检测有，通知是 stub |
| R5 | L228 | import 示例 `from '@/audit/decorators/auditable.decorator'` | 路径错 |

### 📕 01-prd.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| P1 | L122 (F4.4) | 数据库规则 ✅ "PostgreSQL 触发器禁止 UPDATE/DELETE" | 不存在 |
| P2 | L153 (F7.4) | 告警通知发送 📋（待集成）| **唯一诚实的标注**，应保留作为黄金标准 |
| P3 | L150-152 (F7.1-F7.3) | "✅ 完整性告警 / 敏感操作告警 / 异常模式告警" | 检测✅ 通知❌（stub） |
| P4 | L440 验收 | "[x] 审计日志不可修改（数据库层 Trigger）" | 假 |
| P5 | L122 vs L631(arch) | PRD 写"P1 ✅"，Architecture 写"可选" | 文档内部自相矛盾 |
| P6 | L444 | "[x] GDPR 数据访问报告" | **真的有**（保留） |
| P7 | L401-404 | 查询时间跨度限制（90 天 / 1 年 / 30 天 / 10 万条）| **待核实**：未验证代码是否真做了这些限制 |
| P8 | L408-410 | 性能指标"<20ms / <200ms" | **待核实**：没看到 benchmark 证据 |

### 📕 03-architecture.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| A1 | L243-260 | `DatabaseChangeLog.timestamp` | schema 里是 `createdAt` |
| A2 | L477 | `HashChainService.calculateHash()` | 实际方法名 `generateHash()` |
| A3 | L496 | `verifyChain()` | 实际 `verifyHashChain()` |
| A4 | L524-559 | `AuditService.log()` 示例代码缺 HMAC 签名生成 + 缺 alertService 异步触发 + 缺事务 | 实际版本 1170 行有这些 |
| A5 | L446 | 目录结构 `backend/src/audit/` | 错 |
| A6 | L456-458 | 装饰器分文件 `sensitive.decorator.ts` `financial.decorator.ts` | 实际三个装饰器都在 `auditable.decorator.ts` 同一文件 |
| A7 | L631 | "数据库规则：禁止 UPDATE/DELETE（可选）" | 跟 PRD 矛盾 + 实际未实现 |
| A8 | L860-862 | env 配置示例只列 4 个 | 配置文档 11.md 列了更多（HMAC/告警相关） |
| A9 | L869-872 | "相关文档"链接用旧文件名（PRD.md / API.md / TODO.md）| 实际是 01-prd / 07-api / 10-roadmap |

### 📕 07-api.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| API1 | L161-182 端点表 | 列出 12 个端点 | 实际 20 个，**漏 8 个**：reports/sox, reports/gdpr/:userId, reports/anomaly, reports/sox/export, alerts/batch-check, alerts/user/:userId, alerts/detect-failures, alerts/detect-anomaly |
| API2 | L148-151 | 4 项时间跨度限制 | **待核实** vs 代码 |
| API3 | (待检查) | 报表/告警接口的 schema、参数、错误码 | 整个 8 个端点的接口文档可能完全缺失 |

### 📕 10-roadmap.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| RM1 | L20 (Phase 7) | "[x] 数据库触发器 - 禁止 UPDATE/DELETE audit_logs" | 不存在 |
| RM2 | L113 (Phase 4) | "AuditController - 12 个 API 端点" | 实际 20 |
| RM3 | L170 已解决 | "数据库级别保护 → 触发器禁止 UPDATE/DELETE - 2025-12-19" | 虚假 |
| RM4 | L175 已解决 | "审计日志不可篡改 → 哈希链 + 定时校验" | 部分真：哈希链有 race 漏洞，严格不算"不可篡改" |
| RM5 | L184-187 进度统计 | "Phase 7 完成 60% / 总体 85%" | 高估，按真实情况 Phase 7 接近 30%，总体 70% |
| RM6 | L23 | "告警服务与通知系统集成（待集成）" | **诚实标注，保留** |

### 📕 99-changelog.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| CL1 | L78-80 | "[1.1.0] 数据库触发器覆盖三个表" | **3 表都没有** |
| CL2 | L27-29 | "234 个 @Auditable / 181 个 @Sensitive / 83% 覆盖率" | 288 / 174 / 57% |
| CL3 | L51-52 | "DatabaseChangeLog / SensitiveOperationLog 数据模型" | 表存在但**全仓零写入**，dead table |
| CL4 | L102-108 | "实时告警系统 - 全部 ✅" | 检测✅ 通知❌ |
| CL5 | L86 | "AUDIT_HMAC_SECRET 安全警告" | 实际只 warn 不阻断启动，不构成"安全保障" |

### 📕 11-configuration.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| C1 | L153-167 + L463-467 | 演示 trigger 是否生效的 SQL（"应该被 trigger 阻断"）| trigger 不存在，按文档跑 SQL 会成功，**误导用户**以为系统坏了 |
| C2 | L19, L402 | "AUDIT_HMAC_SECRET 已设置为强密钥" | 实际缺失只 warn 不阻止启动 |
| C3 | L266-296 | "告警规则配置在 alert.service.ts" | 配置在那，但通知发送是 TODO stub |
| C4 | L308-311 | `alert_recipients` 配置表设计 | **待核实** schema 里是否真有这张表 |

### 📕 12-integration-guide.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| IG1 | L68, L180 | import 路径 `from '@/audit/decorators/auditable.decorator'` | 路径错 |
| IG2 | (整篇示例) | Service 层注入 `AuditService` 路径 | 同上路径错 |

### 📕 13-troubleshooting.md
| # | 位置 | 失实内容 | 实际 |
|---|---|---|---|
| TS1 | L104 (问题 3) | "Cannot find module auditable.decorator" 解决方案 | 给的 import 路径可能也是过期的 `@/audit/...` |
| TS2 | L437 已知问题 | "Issue 1: 审计拦截器未自动注册" | 实际 `app.module.ts` 已注册，可能 issue 早已解决但文档未更新 |

---

## C. 修复策略（按动作分类）

### 🔴 必须立刻修（虚假宣传 → 合规风险）
**核心动作**：把所有"✅ 数据库触发器"统一改成 "🚧 计划中（PR-X 落地）"。
- P1, P4, A7, RM1, RM3, CL1, R2 共 **7 处**同源错误，一改就全好

**关键句模板**：
> ⚠️ **状态修正（2026-05-03）**：本节描述的"数据库触发器禁止 UPDATE/DELETE"在 v1.0.0 / v1.1.0 changelog 中标为已完成，但代码核查发现实际未实现。计划在 [PR-2: 合规修复] 中落地。在此之前，审计日志的不可篡改性**仅依赖应用层（哈希链 + HMAC + 定时校验）**，DBA 直接 SQL 仍可改写。

### 🟡 修正失真数据
- 集成数字（R3 / CL2）：改成自动生成（加个 5 行脚本 `scripts/ops/audit-coverage.sh` 跑出真实数字）
- API 端点数（RM2 / API1）：扫源码生成
- 进度百分比（RM5）：重算

### 🟢 修正路径与示例
- 所有 `backend/src/audit/` → `backend/src/core/observability/audit/`（R1, A5, IG1, IG2, TS1）
- import 路径示例（R5, IG1, IG2）
- ER 图字段名（A1）
- 方法名（A2, A3）
- 装饰器文件结构（A6）
- AuditService 示例代码补 HMAC + alert 触发（A4）
- 删除"可选"措辞统一口径（A7 vs P1）

### 🔵 待核实（先标记，后核）
- 查询时间跨度限制是否真有代码实现（P7, API2）
- 性能指标 benchmark（P8）
- alert_recipients 表是否存在（C4）
- troubleshooting 已知问题是否还存在（TS2）

### ⚫ 删除/重写（dead 内容）
- DatabaseChangeLog / SensitiveOperationLog 在 PRD/Arch/Changelog 里宣传的"已实现" → 改成"表结构预留，写入逻辑待 D1 决策"
- Configuration 11.md 的 trigger 测试 SQL（C1）→ 删掉或加大警告"在 PR-2 落地前会显示 UPDATE 成功，这是已知文档/代码差距"

---

## D. 决策点（开工前需拍板）

### D1. 两张 dead table 怎么办？
- `audit_database_change_log`：**字段级 + 跨表 DB 变更追踪**（一次审批操作动了哪些表的哪些字段）
- `audit_sensitive_operation_log`：**敏感操作的审批 + MFA 留痕**（双人复核 / 4-eyes principle / MFA verified）

两者都是为**未实施的更高合规需求**预留。当前产品没有 MFA / 双人审批流程。
- A. 补写入逻辑（在 `AuditService.log()` 同事务写）
- B. 删表（一次性 migration drop）
- C. **推荐**：删 `database_change_log`（落地成本太高），留 `sensitive_operation_log`（跟"告警接通"PR 天然搭配，结构已就绪）

### D2. 41 个未集成 controller 这次纳入吗？
- 大头：`meeting-attendance` 整片（10+ 个）、`knowledge-base`、`organization/adp/dingtalk`、`approval/process-admin`、`form-engine` review/callback、`devtracker`
- **推荐**：不纳入本次。这是"业务覆盖决策"，每个模块 owner 应自己评估（比如 `outlook-webhook` 这种系统回调可能根本不需要审计）。本次只修基础设施 bug，覆盖度问题在 README 里加未集成清单 + 提 issue 让各模块认领

### D3. 写入失败兜底用什么？
- A. DB 死信表 `audit_write_failures`（不增加依赖，但 DB 挂时也写不进去）
- B. Redis/BullMQ 队列（增加依赖，解耦）
- C. **推荐**：本地 jsonl spool + 启动/定时回放（对 DB 故障有韧性，增量复杂度可控）

### D4. 防篡改用 trigger 还是 rule？
- A. `CREATE RULE ... DO INSTEAD NOTHING`（静默吞 UPDATE/DELETE，应用层无感）
- B. `CREATE TRIGGER ... RAISE EXCEPTION`（明确报错，应用层会看到 PrismaClientKnownRequestError）
- 取决于："违规操作发生时希望应用层得到反馈吗？"
- 风险：选 A 时若有合法 UPDATE（如 IntegrityService 写 archivedAt）会被静默吞，需要先 grep 确认无人写

---

## E. 修复 PR 拆分

按 CLAUDE.md "docs 单独 PR" + "高风险路径 prisma/schema/** 必须单独 PR" + "文档先行"原则：

### 文档 PR（先行）

#### Doc-PR-1：状态校准
- **触碰文件**：README, 01-prd, 03-architecture, 10-roadmap, 99-changelog, 11-configuration, 12-integration-guide, 13-troubleshooting
- **关键改动**：所有"✅ 已完成"但实际未做的标记 → "🚧 待 PR-X" 或 "⚠️ 待修复"；触发器相关 7 处统一标记；告警通知相关统一为"🚧 检测就绪、通知接入中"；prd L440 虚假 [x] → [ ]；README 顶部加"## 当前状态修正（2026-05-03）"章节
- **分支**：`docs/audit-status-truthful`

#### Doc-PR-2：路径 + 示例代码同步
- **触碰文件**：README, 03-architecture, 12-integration-guide, 13-troubleshooting
- **关键改动**：`backend/src/audit/` → `backend/src/core/observability/audit/`；import 路径全替换；ER 图字段名 + 方法名 + 装饰器结构；AuditService.log() 示例补 HMAC + alert；相关文档链接修正
- **分支**：`docs/audit-paths-and-examples`

#### Doc-PR-3：API 文档补全 + 数字自动化
- **触碰文件**：07-api, README, 99-changelog, 10-roadmap + 新增 `scripts/ops/audit-coverage.sh`
- **关键改动**：07-api.md 补 8 个端点完整文档（请求 / 响应 / 权限 / 错误码）；加扫描脚本；README 集成数字段改成"运行脚本"；删 changelog 里的具体数字
- **分支**：`docs/audit-api-completion`

### 代码 PR（后做）

#### PR-1（独立）：合规——DB 防篡改 + 哈希链 race fix
- **触碰**：prisma/schema + migration + audit.service.ts
- **关键改动**：加 migration 防篡改 RULE/TRIGGER（D4 决定）；`AuditService.log()` 改 `prisma.$transaction` + advisory lock；写复现脚本
- **分支**：`fix/audit-tamper-proof-and-race`

#### PR-2（独立，安全）：HMAC 密钥强校验
- **触碰**：hash-chain.service.ts + .env.example
- **关键改动**：env 缺失或等于默认值时 throw；CI 强制 .env.example
- **分支**：`fix/audit-hmac-required`

#### PR-3（独立，可靠性）：写入失败兜底
- **触碰**：audit-log.interceptor.ts + 新增 spool 服务
- **关键改动**：D3 决定的兜底方案
- **分支**：`fix/audit-write-spool`

#### PR-4（独立）：告警通知接通
- **触碰**：alert.service.ts + 接 notification-engine
- **关键改动**：解掉 4 处 TODO
- **分支**：`feat/audit-alert-notification`

#### PR-5（独立）：interceptor 启发式收紧
- **触碰**：audit-log.interceptor.ts + schema entityId 改可空 + 一个 migration
- **关键改动**：`extractEntityId` 没匹配返回 null；`extractEntityType` 改从装饰器参数取；`isFinancialOperation` URL 启发式改成 warn 提示加装饰器（不直接删，避免误改保留期）
- **分支**：`fix/audit-interceptor-heuristics`

#### PR-6（按 D1）：dead table 处理
- 选 A：补写入逻辑（与 PR-1 合并）
- 选 B/C：删表 migration（独立 PR）
- **分支**：`chore/audit-dead-tables`

### 暂不纳入
- `AuditService` / `AlertService` 拆文件：纯重构，先修 bug 后再做
- 41 个未集成 controller 加 `@Auditable`：D2 决策后由模块 owner 单独提

---

## F. 推荐执行顺序

```
Doc-PR-1 (状态校准 / 紧急)
    ↓
Doc-PR-2 (路径 + 示例 / 防新人踩坑)
    ↓
Doc-PR-3 (API 补全 / 体力活)
    ↓
等用户做 D1/D2/D3/D4 决策
    ↓
开始代码修复 PR-1~PR-6（每个代码 PR 完成后，回头同步对应文档段）
```

每个 Doc-PR 估计 30-60 分钟，3 个 PR 当天可以连开。

---

## G. 反思 / 未来防患

文档失实的根因：**v1.0.0 (2025-12-18) 和 v1.1.0 (2025-12-19) 这两次 changelog 是按"计划完成"标的 ✅，不是按"代码 merged"标的**。

建议在 `docs/standards/06-documentation-system.md` 加一条规则：**Changelog 里的 ✅ 必须对应已 merged 的 commit hash**。可以放在 Doc-PR-1 一起做，避免下次重蹈覆辙。

---

## 附录 H：本次盘点的关键证据命令

```bash
# 装饰器使用次数
grep -rEn "@(Auditable|Sensitive|Financial)\(" backend/src --include="*.ts" \
  | awk -F'@' '{print $2}' | awk -F'(' '{print $1}' | sort | uniq -c
# 实测: 288 Auditable / 174 Sensitive / 5 Financial

# Controller 覆盖率
total=$(find backend/src -name "*.controller.ts" | wc -l)         # 96
covered=$(grep -lE "@(Auditable|Sensitive|Financial)\(" backend/src --include="*.controller.ts" -r | wc -l)  # 55
# 未集成: 41 / 覆盖率: 57%

# 触发器/防篡改 SQL 全仓搜
grep -rEn "CREATE TRIGGER|CREATE RULE|REVOKE.*UPDATE|REVOKE.*DELETE|DO INSTEAD" backend/prisma
# 零结果

# Dead table 写入路径
grep -rEn "auditDatabaseChangeLog|audit_database_change_log" backend/src
grep -rEn "auditSensitiveOperationLog|sensitive_operation_log\b" backend/src
# 都是零结果

# AlertService TODO
grep -nE "TODO|FIXME" backend/src/core/observability/audit/services/alert.service.ts
# 4 处: :187, :374, :522, +注释掉的 :193 :523

# HMAC 默认密钥
grep -nE "hmacSecret|AUDIT_HMAC|HMAC_SECRET" backend/src/core/observability/audit/services/hash-chain.service.ts
# 行 17-21: 默认值 'default-audit-hmac-secret-change-in-production-2025'
```

---

**报告结束**

下一步动作由用户决策（D1-D4 + Doc-PR 顺序 + 是否要顺手在 standards 加 changelog 规则）。
