docs(plan): A5 实施报告与计划回写

新增 A5 审计与日志实施报告;回写开发计划状态、§6.6 完成情况与 WBS 状态;README 文档索引增加 A5 报告。
This commit is contained in:
Stev_Wang committed 2026-11-04 16:28:30 +08:00
1 parent 26ffe97927
commit 83aa04b856
3 files changed
+163 -3

No files matched your search

@@ -0,0 +1,157 @@
# 灵犀 A5 审计与日志 · 实施报告 V0.1
| 项 | 内容 |
| --- | --- |
| 实施日期 | 2026-11-04 |
| 批次 | A5(审计与日志) |
| 状态 | **已完成**(开发 + 全部门禁 + 浏览器 e2e 通过;未提交 git) |
| 依据 | 开发计划 V0.1 §6.6 / §7.5;PRD V1.6 §5.4.6 / R-05;技术架构 V0.4 §5.4.5 / §9.7;工程规范 08 |
| 视觉基线 | A0「方向 A · 云白蓝」;用户选定 **操作日志列表 A / 日志详情 A / 运行日志 C** |
| 迁移 | `prisma/migrations/20261104054622_a5_audit_logs/` |
| 前端路由 | `/system/audit-logs`、`/system/audit-logs/:id`、`/system/logs` |
---
## 1 范围与完成度
| 任务 | 内容 | 状态 |
| --- | --- | --- |
| A5-BE-01 | 审计表与 append-only(月分区 / 复合主键 / DB 触发器 / `lingxi_app` 最小权限) | ✅ |
| A5-BE-02 | 审计采集(`@Audit` + 拦截器落库、成功 / 失败双写、前后值、脱敏) | ✅ |
| A5-BE-03 | 审计检索与导出(多条件游标检索、详情 diff、异步 CSV、签名下载、导出自身留痕) | ✅ |
| A5-BE-04 | 敏感访问与保留归档(查看审计留痕、保留期配置、归档占位) | ✅ |
| A5-INF-01 | 运行日志与观测入口(Pino / trace_id / 日志平台配置与外链;OTel 待接) | ✅(OTel 待确认) |
| A5-FE-01 | 操作日志列表与详情(方案 A:筛选卡 + 全宽表格 + 游标分页 / 独立详情 + diff + 原始 JSON) | ✅ |
| A5-FE-02 | 导出与只读(560 抽屉、进度轮询、历史、签名下载、权限控制) | ✅ |
| A5-QA-01 | 不可篡改与留痕用例(7 类采样 + append-only + 浏览器 e2e) | ✅ |
> 侧栏同步更名:`审计日志` → **`操作日志`**、`客户字段` → **`字段管理`**,新增 **`运行日志`**;A1–A4 既有页面与功能未改动。
---
## 2 交付物
### 2.1 文档与契约
- 技术架构 §5.4.5 / §9.7 对应实现已回写本报告;开发计划 §6.6 / §7.5 / §13 状态与 WBS 已回写。
- `e2e/README.md` 增加 A5 用例与运行命令。
- UI 原型与实现截图:`design/a5-ui-v1/`(A/B/C 三套原型、对比页、评审记录、实现截图)。
### 2.2 共享契约(`packages/shared`)
- `audit/audit-actions.ts`:10 个审计分类(含 `other` 兜底)、54 个动作(现有 36 个 `@Audit` + A5 新增 + asset/task/sensitive 预留),`auditActionMeta` 未知 code 兜底。
- `audit/audit-diff.ts`:`buildAuditDiff` 纯函数(对象一层展开、数组 index、权限点数组增删语义);前后端共用。
- `schemas/audit.ts`:列表 / 详情 / 动作目录 / 导出创建 / 导出任务(含 `download_token`)/ 导出历史的 Zod 契约。
- `config/system-configs.ts`:新增 `audit` 分组与 3 项配置:`audit.retention_months`(默认 12,min 12)、`audit.export_max_rows`(默认 50,000)、`audit.logs_platform_url`(public,供运行日志页读取)。
- 单测:契约、动作目录、diff、配置校验(shared 合计 163)。
### 2.3 数据库与种子
迁移 `20261104054622_a5_audit_logs`(expand-only,新增表,不改历史迁移):
- `audit_logs`:`RANGE (occurred_at)` 按月分区,复合主键 `(id, occurred_at)`;字段覆盖主体 / 动作 / 分类 / 对象 / 客户 / 结果 / 请求 / trace / IP / UA / 前后值 / 原因 / 错误 / 耗时;6 组索引。
- `audit_logs_ensure_partitions(months_ahead int)`:幂等创建当月 ~ +N 月分区;迁移时创建 14 个月;应用启动 + 每日定时调用。
- append-only:`BEFORE UPDATE/DELETE` 行触发器 + `BEFORE TRUNCATE` 语句触发器(`insufficient_privilege`);`REVOKE UPDATE/DELETE/TRUNCATE FROM PUBLIC`;创建 `lingxi_app` NOLOGIN 角色并仅授予审计表 `SELECT/INSERT`。
- `audit_export_jobs`:任务状态 / 进度 / `truncated` / 文件信息 / 过期 / 下载计数 / `download_count`。
- 种子:`system_configs` 27→29 项(A5 新增 3);6 条纯追加演示审计(固定 ULID,重复执行跳过);`SEED.md` 更新。
### 2.4 后端(`apps/api`)
| 模块 | 接口 |
| --- | --- |
| platform/audit | `GET /audit-logs`、`GET /audit-logs/actions`、`GET /audit-logs/{id}` |
| platform/audit | `POST /audit-logs/export`、`GET /audit-logs/exports`、`GET /audit-logs/exports/{id}` |
| platform/audit | `GET /audit-logs/exports/{id}/download?token=`(`@Public` + HMAC 短时效) |
- `AuditInterceptor`:成功 / 失败都落库;响应前 `await`,写入失败只记 Pino、不影响主请求;`AUDIT_SINK` 可选注入,未装配退化为仅 Pino;修复原 `defer(ALS.run())` 导致 handler 内 `patchAudit` 丢失上下文的隐患。
- 采集覆盖:登录 / 登出 / 登录失败、角色与权限、用户 / 部门、客户 / 行业 / 客户组 / 客户字段、字典类型 / 条目、系统配置;前后值与 targetName / customerId / customerName。
- 敏感访问:审计列表与详情本身写 `audit.log.view`;导出与下载分别写 `audit.export` / `audit.export.download`。
- 导出:异步 `setImmediate`(A6 任务协议就绪后替换)、分批 CSV(UTF-8 BOM、转义)、`audit.export_max_rows` 截断标记、本地 `apps/api/.data/audit-exports/`、启动恢复卡死任务、HMAC 签名下载并计数。
- 保留 / 归档:`AuditRetentionService` 仅返回计划(`supported:false`),不 UPDATE/DELETE 审计数据(QT-10 待确认)。
- 查询:所有过滤在 SQL `where` 完成;稳定排序 `occurred_at DESC, id DESC`;关键字覆盖对象名 / 对象 ID / 请求路径 / 原因 / 操作人姓名 / 账号 / trace_id / IP。
### 2.5 前端(`apps/web`)
| 页面 | 路由 | 实现 |
| --- | --- | --- |
| 操作日志 | `/system/audit-logs` | 方案 A:页头只读标识 + 查看留痕提示 + 两行筛选(关键字 / 分类 / 操作人 / 客户 / 结果 / 时间预设 + 自定义日期)+ 全宽表格 + 真实游标「加载更多」 |
| 日志详情 | `/system/audit-logs/:id` | 方案 A:左 diff / 完整原始 JSON,右上下文 / 只读说明 / 相关记录;loading / error / 404 三态;失败请求可打开 |
| 运行日志 | `/system/logs` | 方案 C:平台入口卡 / trace 检索卡 / 服务状态卡 / 最近错误卡;真实数据仅 `audit.logs_platform_url`,其余为诚实空态 |
- 导出:560 SideSheet,创建 → 1.5s 轮询 → 成功 / 失败 / 过期 / 历史 / 刷新 / 签名下载;创建成功失效审计列表,使 `audit.export` 记录进入列表。
- 权限:`audit:read` 守卫列表 / 详情 / 运行日志;`audit:export` 控制导出入口(无权限不渲染)。
- 侧栏:`操作日志`(紧随数据字典)、`运行日志`、`字段管理`;面包屑详情补「管理中心 / 操作日志 / 日志详情」。
- 适配:1024 宽隐藏 IP / trace 并收窄列宽,保留 时间 / 操作人 / 操作 / 对象 / 客户 / 结果 / 查看;1440 九列完整。
- 数据层:`api/audit.ts`(shared Zod parse)、`hooks/queries/use-audit.ts`(InfiniteQuery / 详情 / 动作目录 / 导出轮询 / 创建 mutation)、`api/query-keys.ts`、`endpoints.ts`、`apiBaseUrl()`。
- 样式:`styles/audit.css`、`styles/run-logs.css`,只用 `--lx-*` token。
---
## 3 验证结果
| 门禁 | 命令 | 结果 |
| --- | --- | --- |
| 格式 | `pnpm format:check` | ✅ |
| Lint | `pnpm lint` | ✅ 0 error / 0 warning |
| 类型 | `pnpm typecheck` | ✅ 10/10 tasks |
| 单测(shared) | `pnpm --filter @lingxi/shared test` | ✅ 16 files / 163 tests |
| 单测(api) | `pnpm --filter @lingxi/api test` | ✅ 24 files / 164 tests(lines 65.75%,阈值 60) |
| 单测(web) | `pnpm --filter @lingxi/web test` | ✅ 32 files / 187 tests(lines 92.72%) |
| 集成 | `pnpm --filter @lingxi/api test:int` | ✅ 8 files / 70 tests(含 audit 12) |
| 构建 | `pnpm build` | ✅ 8/8 tasks |
| OpenAPI | `pnpm openapi:diff` | ✅ 无破坏性变更 |
| 浏览器 e2e | `A5_E2E=1 pnpm exec playwright test e2e/a5-audit-logs.spec.ts` | ✅ 5/5(约 57s) |
**e2e 覆盖**:列表 / 查看留痕提示 / 分类筛选 / 重置;详情 diff / 完整原始 JSON / trace / IP / 请求 / 耗时;导出创建 → 轮询完成 → 导出历史 → API 断言 `download_token` + CSV 200 → 列表新增 `audit.export`;无 `audit:read` → 403;有 `audit:read` 无 `audit:export` 不渲染导出;运行日志未配置空态 → 配置后外链出现。
**数据库实测**:`UPDATE / DELETE / TRUNCATE audit_logs` 均被 PG 拒绝;`SET LOCAL ROLE lingxi_app` 下 INSERT / SELECT 成功、UPDATE 被权限拒绝;分区函数重复执行 `created=0`;`prisma migrate status` up to date;seed 二次执行配置不覆盖、演示审计不重复。
**浏览器人工验收**(真实 API + PG + seed):1440 列表 / 详情 / 导出抽屉 / 运行日志(已配置 / 未配置)、1024 列表;无控制台错误、无横向溢出,body 不出现纵向滚动。
---
## 4 关键口径与实现说明
1. **append-only 双保险**:DB 触发器阻止任何角色 UPDATE/DELETE/TRUNCATE;生产应用账号应使用 `lingxi_app`(仅审计表 SELECT/INSERT)。本地仍用 postgres,由触发器兜底。
2. **分区无 default 分区**:迁移建 14 个月,启动 / 每日 `ensure` 继续创建;写入范围由 ensure 保证,避免 default 分区与后续 ATTACH 冲突。
3. **审计查看留痕**:列表 / 详情访问写 `audit.log.view`(LOG-14);因此频繁轮询列表会产生较多浏览记录,是否采样 / 仅详情留痕待产品确认。
4. **导出自身留痕**:创建写 `audit.export`,下载写 `audit.export.download`;创建成功前端失效列表,保证新记录可见。
5. **权限口径**:`audit:read` / `audit:export` 为特殊权限,默认关闭;无权限不渲染导出入口,接口仍校验(40303)。
6. **运行日志与审计分离**:运行日志只提供日志平台入口与 trace 检索指引,不写入业务库;服务状态 / 错误列表无后端接口,页面用空态说明,不伪造数据。
7. **时间筛选包含当天**:预设「今天 / 近 7 天 / 近 30 天」`to` 取当天 23:59:59.999,保证列表刷新后能看到当天新产生的审计记录。
8. **不伪造统计**:后端未提供 summary 接口,列表页不展示 KPI 数字,只显示真实「已加载 N 条 / 还有更多 / 已全部加载」。
9. **导出预计条数**:无服务端 count 接口,抽屉显示「当前筛选已加载 N 条,实际以服务端筛选为准」,不编造条数。
10. **详情 diff label**:后端只返回路径(label 为 null),前端用本地路径中文名映射兜底。
---
## 5 与计划的差异与待确认
| 编号 | 项 | 现状 |
| --- | --- | --- |
| A5-D-01 | 七类留痕 | 登录 / 权限 / 客户与字典等配置 / 导出 / 敏感访问已实测;资产(B1)与任务(A6)接口未建设,已登记 action code 与预留,未伪造数据 |
| A5-D-02 | OTel 接入 | SDK 属新增运行时依赖,本轮未引入;Pino 字段、trace_id 贯通、审计与运行日志分离已就绪,运行日志页标注「OTel 待接入」 |
| A5-D-03 | 运行日志服务指标 | 无指标 / trace / 错误查询接口,页面按方案 C 布局但服务状态与最近错误为空态,不伪造 |
| A5-D-04 | 保留期 | 默认 / 下限 12 个月(min 12 / max 60),QT-10 最终值确认后改 shared 定义即可 |
| A5-D-05 | 审计查看留痕频率 | 列表每次请求都留痕,符合 LOG-14;如需采样 / 去重需产品确认 |
| A5-D-06 | 导出存储 | A5 使用本地 `apps/api/.data/audit-exports/`,B3 / A6 接入对象存储与统一任务协议后替换;过期文件未定时物理清理 |
| A5-D-07 | 导出并发 | 未做限流,待 A6 / 运维策略 |
| A5-D-08 | e2e 审计数据 | 审计表 append-only,e2e 产生的审计记录不清理(仅清理临时账号 / 角色并还原配置),属预期留痕 |
---
## 6 已知遗留
1. 资产 / 任务 / 成本 / 客户资产包接口就绪后接入 `asset.*` / `task.*` / `sensitive.*` 动作(action code 已登记)。
2. 导出文件物理清理、并发限流、对象存储签名 URL(B3 / A6)。
3. OTel SDK 与日志平台真实查询接口(待依赖评审 / 运维提供)。
4. 列表「查看」动作产生 `audit.log.view` 记录,后续可按需调整采样策略。
5. UI 原型中 A/B/C 三套为设计产物(`design/` 目录不入库);实现按用户选定 A + A + C 落地。
---
## 7 下一步
- 按计划进入 **A6 任务中心与队列骨架**:审计导出将迁移到统一任务协议(进度 / 重试 / 取消 / 幂等)。
- A7 AI 配置可直接复用审计采集与 `@Audit` 装饰器;资产 / 任务接入后补齐七类留痕。
- QT-10 / Q-08 保留期、OTel 依赖、审计查看采样策略确认后回写本报告。
@@ -10,7 +10,7 @@
| 文档名称 | 灵犀 · 信息流素材生产平台 项目开发计划 |
| 版本 | V0.1(初稿,待评审) |
| 日期 | 2026-11-02 |
| 状态 | **执行中**:A0 工程骨架与契约基线已完成(2026-11-02);A1 身份认证与用户管理已完成(2026-11-03);A2 角色权限与数据范围已完成(2026-11-03);A3 客户与行业目录已完成(2026-11-04 最终验收);A4 数据字典与系统配置已完成(2026-11-04);下一步 A5 |
| 状态 | **执行中**:A0 工程骨架与契约基线已完成(2026-11-02);A1 身份认证与用户管理已完成(2026-11-03);A2 角色权限与数据范围已完成(2026-11-03);A3 客户与行业目录已完成(2026-11-04 最终验收);A4 数据字典与系统配置已完成(2026-11-04);A5 审计与日志已完成(2026-11-04);下一步 A6 |
| 需求基线 | PRD V1.6([灵犀-信息流广告素材一站式生产平台PRD.md](../PRD/灵犀-信息流广告素材一站式生产平台PRD.md)) |
| 技术基线 | 技术架构 V0.4([灵犀_信息流素材生产平台_技术架构_V0.1.md](../技术架构/灵犀_信息流素材生产平台_技术架构_V0.1.md))+ `AGENTS.md` V1.0 + `docs/工程规范/` V1.0 |
| 覆盖范围 | **M0 任务级;M1–M4 迭代级 + 任务组级;M5 预研边界(不排期)** |
@@ -384,7 +384,7 @@ flowchart LR
| **A2** | ✅ 已完成 | 角色权限与数据范围 | A1 | 5.5 | 4–5 日 | W2 | 角色可配;SoD 命中拒绝;特殊权限默认关闭;范围过滤生效 |
| **A3** | ✅ 已完成 | 客户与行业目录 | A1 / A2 | 7 | 4–5 日 | W2 | 行业 / 客户 / 客户组可用;唯一性;状态只读;字段审批 |
| **A4** | ✅ 已完成 | 数据字典与系统配置 | A3 | 4 | 3–4 日 | W3 | 三层合并;编码不可改;阈值同源;配置版本 + 审计 |
| **A5** | 未开始 | 审计与日志 | A1 / A4 | 4 | 3–4 日 | W2–W3 | 关键操作全留痕;不可篡改;导出自身审计 |
| **A5** | ✅ 已完成 | 审计与日志 | A1 / A4 | 4 | 3–4 日 | W2–W3 | 关键操作全留痕;不可篡改;导出自身审计 |
| **A6** | 未开始 | 任务中心与队列骨架 | A0 / A1 | 6 | 4–5 日 | W2–W3 | 提交 → SSE → 完成 / 失败;幂等;取消 / 重试 |
| **A7** | 未开始 | AI 配置与调用底座 | A0 / A1 / A5 | 6 | 4–5 日 | W3 | 四层配置;Mock 调用;密钥不泄露;账本可查 |
| **B0** | 未开始 | 行业包与表单设计器 | A3 / A4 | 8 | 6–7 日 | W4 | 电商行业包 v1;七项校验;双人发布;快照 |
@@ -631,6 +631,8 @@ PRD §7 把 **M-27(素材元素级拆解)** 与「自动字幕 → TTS 配
**本批门禁**:关键操作全留痕;审计不可改;导出本身留痕;运行日志与审计分离。
> **A5 完成情况(2026-11-04)**:8/8 项 WBS 全部 ✅。`audit_logs` 按月分区(复合主键 + append-only 触发器 + `lingxi_app` 最小权限)、`@Audit` 拦截器成功 / 失败双写与脱敏、多条件游标检索 / 详情 diff / 异步 CSV 导出 / HMAC 短时效下载、敏感访问与保留归档占位、Pino trace_id 与运行日志平台入口全部落地;前端按用户选定 **操作日志列表 A / 详情 A / 运行日志 C** 实现 `/system/audit-logs`、`/system/audit-logs/:id`、`/system/logs`,侧栏更名「操作日志」「字段管理」并新增「运行日志」。门禁:`format:check / lint / typecheck / test` 全绿(shared 163 / api 164 / web 187,共 514),`test:int` 70 项(8 文件,含 audit 12),A5 浏览器 e2e 5/5(约 57s),`build` 8/8,`openapi:diff` 无破坏性变更;实现记录、截图与遗留见 [A5 实施报告](./灵犀_A5审计与日志_实施报告_V0.1.md)。已知遗留:OTel SDK(待依赖评审)、运行日志服务指标 / trace 查询接口、导出对象存储与并发限流(B3 / A6)、资产 / 任务动作接入(B1 / A6)、QT-10 保留期最终值。
### 6.7 A6 任务中心与队列骨架
**目标**:所有耗时操作走统一任务协议,前端有统一进度体验。**对应**:架构 §6.4 / §3.3;M-05;`D-02` / `D-08`。