This commit is contained in:
File diff suppressed because it is too large
Load Diff
556
docs/engineering/从零构建Agent友好项目最佳实践.md
Normal file
556
docs/engineering/从零构建Agent友好项目最佳实践.md
Normal file
@@ -0,0 +1,556 @@
|
||||
# 从零构建 Agent 友好项目:Harness 工程实践与文档标准
|
||||
|
||||
> 用途:用于新项目初始化和长期治理。它定义“哪些知识需要存在、放在哪里、按什么格式写、如何验证”。
|
||||
>
|
||||
> 核心结论:OpenSpec 只负责当前行为与行为变更;完整 Harness 还必须让 Agent 能找到事实、运行系统、观察结果、遵守边界、独立验证并持续清理熵。
|
||||
|
||||
## 1. 先澄清:ADR 不是 OpenSpec
|
||||
|
||||
- OpenSpec 的标准 Artifact 是 `proposal`、增量 `specs`、`design` 和 `tasks`。
|
||||
- `design.md` 记录**一次 Change** 的实现设计、取舍、迁移和回滚。
|
||||
- ADR 是业界通用但独立的“长期架构决策记录”,不属于 OpenSpec,也不是新项目必需品。
|
||||
- 默认不建 `adr/`。只有一个跨 Change、长期生效、存在真实竞争方案且无法从代码理解原因的决策,才考虑单独记录;否则留在对应 Change 的 `design.md`。
|
||||
|
||||
## 2. Harness 的完整范围
|
||||
|
||||
OpenAI 的 Harness Engineering 不只是“短 AGENTS.md + docs”。一个完整闭环包含:
|
||||
|
||||
1. **仓库可读**:事实进入仓库,并有清晰索引和单一权威位置。
|
||||
2. **环境可运行**:每个工作区能独立启动、测试和复现。
|
||||
3. **结果可观察**:Agent 能读取日志、指标、追踪、页面和 DOM,而不是靠猜。
|
||||
4. **架构可约束**:依赖方向、边界数据解析和关键不变量可机械检查。
|
||||
5. **任务可判定**:需求被写成可观察、可验证的行为契约。
|
||||
6. **执行可闭环**:Agent 能复现、修改、验证、比较前后结果并处理反馈。
|
||||
7. **知识可维护**:链接、Owner、新鲜度和生成来源可检查,过期内容持续清除。
|
||||
8. **人类掌舵**:人负责目标、优先级和判断点;Agent 负责读取、执行、验证和维护。
|
||||
|
||||
因此,Context 收敛不是“少写文档”,而是让默认 Context 很小、按需 Context 有标准、运行反馈足够强。
|
||||
|
||||
## 3. 从最小集合开始
|
||||
|
||||
### 3.1 所有项目 Day 0 必需
|
||||
|
||||
```text
|
||||
AGENTS.md # 短导航与真正的全局硬约束
|
||||
README.md # 人和 Agent 都可执行的启动入口
|
||||
ARCHITECTURE.md # 一页系统地图和依赖边界
|
||||
openspec/ # 当前行为与行为变更
|
||||
CI / scripts # 格式化、检查、测试、构建命令
|
||||
```
|
||||
|
||||
### 3.2 有真实内容时才创建
|
||||
|
||||
```text
|
||||
docs/integrations/ # 存在第三方契约
|
||||
docs/engineering/ # 存在跨模块、长期工程约束
|
||||
docs/generated/ # 能由命令稳定再生的参考资料
|
||||
RELIABILITY.md # 存在队列、重试、容灾、SLO 等系统性要求
|
||||
SECURITY.md # 存在统一信任边界、敏感数据或威胁模型
|
||||
FRONTEND.md # 前端规模足以需要统一交互/状态/设计规则
|
||||
PRODUCT_SENSE.md # 多团队或 Agent 经常误判产品取舍
|
||||
QUALITY_SCORE.md # 已有明确评分维度和维护机制
|
||||
```
|
||||
|
||||
不要预建空目录、空模板或“以后也许有用”的文档。
|
||||
|
||||
### 3.3 如何理解 OpenAI 文章中的示例目录
|
||||
|
||||
文章展示的是一套成熟仓库的实际结构,不是要求每个项目照抄。与本指南的映射如下:
|
||||
|
||||
| 文章中的示例 | 解决的问题 | 本指南的默认选择 |
|
||||
|---|---|---|
|
||||
| `ARCHITECTURE.md` | 系统地图与边界 | Day 0 创建一页版本 |
|
||||
| `docs/design-docs/`、`DESIGN.md` | 跨范围设计知识 | 单次变更放 OpenSpec `design.md`;只有长期跨 Change 设计才另建 |
|
||||
| `docs/exec-plans/{active,completed}`、`PLANS.md` | 长任务持续执行 | 默认用 OpenSpec `tasks.md`;非行为型长期治理才建唯一执行计划 |
|
||||
| `docs/product-specs/` | 当前产品行为 | 使用 `openspec/specs/`,不再复制一套 |
|
||||
| `docs/generated/db-schema.md` | Agent 可检索的生成事实 | 有稳定生成命令时放 `docs/generated/` |
|
||||
| `docs/references/*-llms.txt` | 将仓库外依赖资料变成 Agent 可读输入 | 仅保存任务高频需要且无法稳定在线获取的官方资料;第三方协议归 `docs/integrations/` |
|
||||
| `FRONTEND.md` | 前端统一边界 | 前端复杂度触发后创建 |
|
||||
| `PRODUCT_SENSE.md` | 产品判断原则 | 重复误判触发后创建 |
|
||||
| `QUALITY_SCORE.md` | 领域/架构质量评分 | 有固定评分和治理动作后创建 |
|
||||
| `RELIABILITY.md`、`SECURITY.md` | 跨系统可靠性和安全约束 | 对应风险出现后创建 |
|
||||
|
||||
关键不是目录名一致,而是:每类知识有唯一权威位置、Agent 可发现、能被验证、不会和 OpenSpec 重复。
|
||||
|
||||
### 3.4 不重复的事实分工
|
||||
|
||||
| 知识 | 权威位置 | 不应放入 |
|
||||
|---|---|---|
|
||||
| 当前可观察业务行为 | `openspec/specs/` | AGENTS、工程规范 |
|
||||
| 拟议行为变化 | `openspec/changes/<change>/` | 主 Specs、路线图副本 |
|
||||
| 系统地图和依赖方向 | `ARCHITECTURE.md` | 每个 Change 重复描述 |
|
||||
| 第三方提供的契约 | `docs/integrations/` | 业务 Spec 原文复制 |
|
||||
| 长期工程约束 | `docs/engineering/` + 检查器 | 任务型 Skill |
|
||||
| 实现事实 | 代码、配置、迁移 | 叙述文档副本 |
|
||||
| 可再生参考资料 | `docs/generated/` | 手工维护文档 |
|
||||
| 任务过程 | OpenSpec Change 或唯一执行计划 | 第二套 TODO/状态台账 |
|
||||
| 完成证据 | 测试、CI、运行输出 | 长期“完成总结” |
|
||||
|
||||
## 4. 每类文档的统一质量契约
|
||||
|
||||
除生成文档外,长期文档至少要让读者回答:
|
||||
|
||||
```yaml
|
||||
purpose: 这份文档解决什么问题
|
||||
scope: 适用与不适用范围
|
||||
source_of_truth: 哪些事实由本文权威维护,哪些只引用
|
||||
owner: 负责判断内容是否仍成立的角色或团队
|
||||
last_verified: 最后通过代码、运行结果或官方来源核验的日期
|
||||
update_triggers: 哪些变更发生时必须同步检查本文
|
||||
verification: 用什么命令或证据判断内容仍有效
|
||||
```
|
||||
|
||||
这不要求所有文件都使用 YAML;要求这些信息明确可找到。
|
||||
|
||||
### 合格标准
|
||||
|
||||
- 写具体规则和可判定结果,不写“注意质量”“合理处理”等口号。
|
||||
- 关键声明有代码路径、命令、测试、Schema 或官方来源支撑。
|
||||
- 复制外部内容时标明来源版本和核验时间。
|
||||
- 一项事实只有一个权威正文,其他位置只链接。
|
||||
- 每个“必须/禁止”都对应机械检查,或明确说明当前为何只能人工审查。
|
||||
- 示例只用于消除歧义;示例不得成为第二份规则。
|
||||
|
||||
### 不合格信号
|
||||
|
||||
- 没有适用范围、Owner 或更新触发条件。
|
||||
- 只有目录介绍,没有依赖方向、入口或验证方式。
|
||||
- 把当前 Bug 写成理想行为,或把未来设计冒充当前事实。
|
||||
- 从代码能直接搜索得到的列表被手工复制并长期维护。
|
||||
- 同一个状态、错误码或字段定义在多份文档中各写一遍。
|
||||
|
||||
## 5. AGENTS.md 标准
|
||||
|
||||
### 必填内容
|
||||
|
||||
1. 项目一句话目标。
|
||||
2. Agent 从代码无法自然发现、且几乎所有任务都适用的硬约束。
|
||||
3. build、test、lint、spec 验证的真实命令。
|
||||
4. 事实源优先级和按需文档入口。
|
||||
5. 禁止直接修改的生成物、敏感路径或外部生产边界。
|
||||
|
||||
### 禁止内容
|
||||
|
||||
- 完整 DTO、Model、路由或迁移操作步骤。
|
||||
- 领域状态机、接口字段表和第三方协议正文。
|
||||
- 已完成任务、历史方案、长 Review 清单。
|
||||
- Agent 能从代码、包管理器或 `--help` 获取的通用知识。
|
||||
|
||||
### 尺寸与验证
|
||||
|
||||
- 80~120 行是目标,150 行是软预算。
|
||||
- 每一行应对大多数任务有价值。
|
||||
- CI 检查链接有效;验证命令必须在干净环境可运行。
|
||||
|
||||
```md
|
||||
# 项目 Agent 指南
|
||||
|
||||
## 项目目标
|
||||
<一句话>
|
||||
|
||||
## 全局硬约束
|
||||
- <规则;对应检查命令或规则文档链接>
|
||||
|
||||
## 验证
|
||||
- Build: `<command>`
|
||||
- Test: `<command>`
|
||||
- Lint: `<command>`
|
||||
- Spec: `openspec validate --all`
|
||||
|
||||
## 事实源
|
||||
- 当前行为:`openspec/specs/`
|
||||
- 系统边界:`ARCHITECTURE.md`
|
||||
- 外部契约:`docs/integrations/`
|
||||
- 工程约束:`docs/engineering/`
|
||||
```
|
||||
|
||||
## 6. ARCHITECTURE.md 标准
|
||||
|
||||
它是系统地图,不是完整设计书。目标是让新 Agent 在几分钟内知道从哪里进入、允许依赖谁、数据如何流动。
|
||||
|
||||
### 必填章节
|
||||
|
||||
1. **系统职责与非职责**:系统解决什么,不解决什么。
|
||||
2. **运行单元**:API、Worker、定时任务、前端、数据库、缓存、外部系统。
|
||||
3. **领域/模块地图**:模块职责、Owner(如有)和主要入口路径。
|
||||
4. **依赖方向**:允许和禁止的跨层、跨模块依赖。
|
||||
5. **关键数据流**:请求、异步事件、回调、读写路径。
|
||||
6. **信任与事务边界**:外部输入在哪里解析,事务在哪里开始/结束。
|
||||
7. **验证方法**:结构测试、依赖检查、启动或 smoke 命令。
|
||||
8. **详细资料索引**:链接 Specs、工程约束和外部契约,不复制正文。
|
||||
|
||||
### 写法标准
|
||||
|
||||
```md
|
||||
## 模块:订单
|
||||
- 职责:创建订单并管理支付前后的状态流转
|
||||
- 入口:`internal/...`
|
||||
- 可依赖:共享错误、支付 Port
|
||||
- 禁止依赖:HTTP Handler、具体支付 SDK
|
||||
- 行为契约:`openspec/specs/orders/spec.md`
|
||||
- 结构验证:`<command>`
|
||||
```
|
||||
|
||||
### 机械检查
|
||||
|
||||
- 用 import/dependency 测试验证禁止依赖。
|
||||
- 用 smoke 命令验证列出的运行单元确实可启动。
|
||||
- 用链接检查验证每个引用存在。
|
||||
- 模块增删、入口迁移或依赖方向改变时触发更新。
|
||||
|
||||
## 7. 外部契约文档标准
|
||||
|
||||
外部契约记录“第三方实际要求我们怎样交互”。它不是本系统的产品需求,也不是 SDK 使用教程。
|
||||
|
||||
### 7.1 目录规则
|
||||
|
||||
```text
|
||||
docs/integrations/<provider>/
|
||||
├── README.md # 当前接入契约与导航
|
||||
├── examples/ # 经脱敏、可复现的请求响应样例(需要时)
|
||||
└── source/ # 无稳定链接的官方原文快照(需要时)
|
||||
```
|
||||
|
||||
官方内容有稳定 URL 时只保存链接和项目所需摘要;不要复制整站文档。
|
||||
|
||||
### 7.2 README 必填字段
|
||||
|
||||
```md
|
||||
# <Provider> 接入契约
|
||||
|
||||
## 元数据
|
||||
- Owner: <角色/团队>
|
||||
- 官方来源: <URL 或 source/ 文件>
|
||||
- 官方版本/发布日期: <值或 unknown>
|
||||
- 项目适用环境: <sandbox/production/region>
|
||||
- 最后核验: YYYY-MM-DD
|
||||
- 更新触发: SDK升级、官方版本变化、字段/签名/错误码变化
|
||||
|
||||
## 接入范围
|
||||
- 使用能力:<项目实际使用的 API/事件>
|
||||
- 不使用能力:<容易误用但明确不接入的能力>
|
||||
|
||||
## 端点与认证
|
||||
| 场景 | Method | URL/Topic | 认证 | 超时 |
|
||||
|
||||
## 请求契约
|
||||
| 字段 | 类型 | 必填 | 约束 | 来源章节 | 项目映射 |
|
||||
|
||||
## 响应与错误
|
||||
| 外部状态/错误码 | 含义 | 可重试 | 项目处理 | 告警 |
|
||||
|
||||
## 签名与回调
|
||||
- 签名原文构造:<精确定义>
|
||||
- 验签步骤:<精确定义>
|
||||
- 时间窗/重放保护:<规则>
|
||||
- 回调确认语义:<响应内容和重试条件>
|
||||
|
||||
## 可靠性
|
||||
- 幂等键:<字段与作用域>
|
||||
- 超时:<连接/请求>
|
||||
- 重试:<次数、退避、仅哪些错误>
|
||||
- 限流:<规则>
|
||||
- 对账/补偿:<触发与入口>
|
||||
|
||||
## 安全与数据
|
||||
- 凭证名称及托管位置:<只写名称,不写密钥>
|
||||
- 敏感字段:<日志脱敏规则>
|
||||
- 来源校验:<证书/IP/签名等>
|
||||
|
||||
## 验证
|
||||
- 本地/沙箱命令:`<command>`
|
||||
- 固定输入:`<fixture>`
|
||||
- 预期结果:`<literal outcome>`
|
||||
- 生产人工验收:<只有真实环境才能完成的最小步骤>
|
||||
```
|
||||
|
||||
### 7.3 质量门禁
|
||||
|
||||
- 所有项目使用的字段都能追溯到官方来源或经确认的真实样例。
|
||||
- 签名、金额单位、时间格式、编码、回调确认内容必须写成精确规则。
|
||||
- 每种外部错误明确:重试、失败、忽略、人工处理中的一种。
|
||||
- 重试必须同时定义上限、退避和幂等保护。
|
||||
- 示例必须脱敏,并可被测试或脚本读取;真实密钥不得进入仓库。
|
||||
- 至少有一个沙箱/契约测试,或明确记录为何只能人工验收。
|
||||
|
||||
### 7.4 不合格示例
|
||||
|
||||
```md
|
||||
调用失败时适当重试。
|
||||
```
|
||||
|
||||
问题:没有错误范围、次数、退避、幂等和最终失败动作,无法实现或验证。
|
||||
|
||||
合格写法:
|
||||
|
||||
```md
|
||||
仅对连接超时和外部错误 E_TEMP 重试;最多 3 次,间隔 1s/2s/4s;
|
||||
每次复用同一幂等键。三次失败后记录 integration failure 并进入人工对账队列。
|
||||
```
|
||||
|
||||
## 8. 工程约束文档标准
|
||||
|
||||
工程约束描述“所有相关代码长期必须保持的性质”。DTO、Model、路由、迁移、注释、依赖边界属于此类,而不是 Skill。
|
||||
|
||||
### 8.1 一条约束的标准格式
|
||||
|
||||
```md
|
||||
### ENG-<AREA>-NNN:<可判定标题>
|
||||
|
||||
- 状态:active | deprecated
|
||||
- 适用范围:<路径、语言、组件或操作>
|
||||
- 规则:MUST/MUST NOT <单一可判定约束>
|
||||
- 理由:<错误成本或架构原因;不复述规则>
|
||||
- 正例:`<最小示例或现有代码链接>`
|
||||
- 反例:`<最小示例>`
|
||||
- 机械检查:`<测试/Lint/脚本命令>` 或 `暂为人工:<原因>`
|
||||
- 例外:<允许条件、批准者、记录位置;无则写“无”>
|
||||
- Owner:<角色/团队>
|
||||
- 最后验证:YYYY-MM-DD
|
||||
- 更新触发:<框架升级、目录调整、事故等>
|
||||
```
|
||||
|
||||
### 8.2 规则拆分标准
|
||||
|
||||
- 一个 ID 只表达一个义务,避免“正确、安全、高性能地处理”。
|
||||
- 适用范围必须能映射到路径或组件。
|
||||
- 正例优先链接仓库内稳定实现,不复制大段代码。
|
||||
- 反例只展示最容易犯且有实际代价的错误。
|
||||
- 规则能由机器判断时,文档必须链接检查器;检查器才是执行门禁。
|
||||
- 例外必须有边界和到期/复审条件,不能写“特殊情况除外”。
|
||||
|
||||
### 8.3 分类
|
||||
|
||||
| 分类 | 应记录 | 首选验证 |
|
||||
|---|---|---|
|
||||
| 架构 | 依赖方向、分层、边界解析 | 结构测试、import Lint |
|
||||
| 数据 | 迁移、主键、金额单位、事务 | Schema/Lint/集成测试 |
|
||||
| API | 路由注册、错误形状、兼容性 | OpenAPI diff、契约测试 |
|
||||
| 可靠性 | 幂等、重试、锁、Outbox | 故障注入、集成测试 |
|
||||
| 安全 | 权限、敏感字段、信任边界 | 安全测试、静态检查 |
|
||||
| 代码品味 | 禁止无价值抽象、复杂度上限 | Lint + Review |
|
||||
|
||||
### 8.4 合格与不合格示例
|
||||
|
||||
不合格:
|
||||
|
||||
```md
|
||||
数据库迁移要谨慎,注意兼容旧数据。
|
||||
```
|
||||
|
||||
合格:
|
||||
|
||||
```md
|
||||
### ENG-DB-003:新增非空列必须可在线回填
|
||||
- 适用范围:`migrations/`
|
||||
- 规则:已有数据表新增非空列时,MUST 先增加可空列或带兼容默认值,完成回填后再收紧约束。
|
||||
- 理由:直接增加无默认值的非空列会使现有数据迁移失败。
|
||||
- 机械检查:`<migration smoke command>`
|
||||
- 例外:仅空表;必须在 Change design 中提供查询证据。
|
||||
```
|
||||
|
||||
## 9. OpenSpec 生成提案的标准
|
||||
|
||||
用户不需要手工填写长表。AI 生成 Change,但生成器和 Reviewer 必须遵守质量门禁。
|
||||
|
||||
### 9.1 Explore 后才能确定的内容
|
||||
|
||||
- 当前行为和证据路径。
|
||||
- 目标、原因、范围和非目标。
|
||||
- 角色、资源、前置状态和数据范围。
|
||||
- 状态、金额、权限、事务、并发、幂等和外部失败边界。
|
||||
- 已知事实、推断和真正需要人决策的问题。
|
||||
|
||||
### 9.2 Proposal
|
||||
|
||||
只回答为什么、做什么、不做什么、影响哪些 Capability。不得塞入实现步骤和通用工程规范。
|
||||
|
||||
### 9.3 Requirement / Scenario
|
||||
|
||||
```md
|
||||
### Requirement: <一个可观察义务>
|
||||
系统 SHALL <测试者无需阅读实现即可判定的结果>。
|
||||
|
||||
#### Scenario: <场景>
|
||||
- **GIVEN** <角色、数据、前置状态>
|
||||
- **WHEN** <一个动作>
|
||||
- **THEN** <可观察结果>
|
||||
- **AND** <副作用或必须保持的不变量>
|
||||
```
|
||||
|
||||
门禁:
|
||||
|
||||
- 一个 Requirement 一个主要义务。
|
||||
- Scenario 有具体前置条件、触发和结果。
|
||||
- 覆盖主流程以及代价最高的权限、失败、边界或重复场景。
|
||||
- 不使用“体验良好”“正确处理”“高性能”等不可判定词。
|
||||
- 不写函数名、表名、ORM、缓存或队列选型。
|
||||
- Brownfield 的错误现状也按 As-Is 写入;Spec 描述事实不等于认可设计。
|
||||
|
||||
### 9.4 Design
|
||||
|
||||
记录本 Change 的架构通道、数据与事务、并发、外部失败、兼容、迁移、发布、回滚和真实取舍。简单且实现显然时保持简短。
|
||||
|
||||
### 9.5 Tasks
|
||||
|
||||
- 按可独立验证的纵向切片拆分,不按 Model/Service/Handler 水平拆分。
|
||||
- 每项映射 Requirement/Scenario,并包含最小验证命令和预期结果。
|
||||
- 适用时同时验证响应、持久化、副作用和失败不变量。
|
||||
- 实施发现 Artifact 与事实冲突时同步修订,不静默跳过或扩大任务。
|
||||
|
||||
## 10. Generated docs 标准
|
||||
|
||||
仅当内容能稳定再生且 Agent 经常需要查询时创建,例如数据库 Schema、OpenAPI 摘要或配置清单。
|
||||
|
||||
每份生成文件顶部必须包含:
|
||||
|
||||
```md
|
||||
<!-- GENERATED FILE: DO NOT EDIT -->
|
||||
- Source: <代码/Schema 路径>
|
||||
- Command: `<生成命令>`
|
||||
- Generator version: <版本>
|
||||
- Generated at: <时间或来源 commit>
|
||||
```
|
||||
|
||||
CI 重新生成并检查 diff。生成结果没有稳定命令时,它就不是 generated doc,而是会腐烂的手工副本。
|
||||
|
||||
## 11. 唯一执行计划标准
|
||||
|
||||
一般功能直接使用 OpenSpec `tasks.md`,不要再建 `docs/exec-plans/`。只有不改变产品行为、跨多天且 OpenSpec 不适配的迁移/治理任务,才使用单独执行计划。
|
||||
|
||||
必填内容:
|
||||
|
||||
- 目标和完成定义。
|
||||
- 范围、非目标和受保护路径。
|
||||
- 前置依赖和顺序。
|
||||
- 可恢复的任务清单,每项含验证。
|
||||
- 当前进度,只在这一处更新。
|
||||
- 关键决策与新发现。
|
||||
- 回滚条件和命令。
|
||||
- 最终验收命令及预期结果。
|
||||
|
||||
Manager、Executor、Auditor 可以分工,但只能共享这一份任务契约;Harness state 和报告只是可丢弃证据。
|
||||
|
||||
## 12. Agent 可操作环境标准
|
||||
|
||||
### 12.1 独立运行
|
||||
|
||||
- 每个 worktree/工作区可使用不同端口和隔离的临时数据启动。
|
||||
- README 提供一条启动命令、一条测试命令和一条重置命令。
|
||||
- 依赖、种子数据和环境变量有可复制的本地默认值;真实凭证不入库。
|
||||
|
||||
### 12.2 浏览器与 UI
|
||||
|
||||
有 UI 时,Agent 应能:
|
||||
|
||||
- 启动应用并通过浏览器/CDP 操作关键流程。
|
||||
- 保存失败前后的截图或视频。
|
||||
- 读取 DOM、Console error 和失败网络请求。
|
||||
- 用稳定测试定位器,而非脆弱坐标。
|
||||
|
||||
### 12.3 可观测性
|
||||
|
||||
本地至少能按 request/correlation ID 串起:
|
||||
|
||||
- 结构化日志。
|
||||
- 关键指标。
|
||||
- 跨进程或异步链路追踪(系统存在此类链路时)。
|
||||
|
||||
提供最小查询命令。临时本地可观测栈应可一键启动和销毁,运行数据不作为长期事实源。
|
||||
|
||||
### 12.4 自主验证闭环
|
||||
|
||||
```text
|
||||
复现问题 → 捕获基线 → 修改 → 运行最小测试 → 启动系统
|
||||
→ 检查日志/指标/页面 → 对比前后 → 完整门禁 → 提交审查
|
||||
```
|
||||
|
||||
只有产品取舍、不可逆操作、真实生产权限或相互冲突的事实需要升级给人。
|
||||
|
||||
## 13. Reliability、Security、Product Sense、Quality Score
|
||||
|
||||
这些不是所有项目的固定作业。
|
||||
|
||||
### RELIABILITY.md:何时创建
|
||||
|
||||
当系统出现 SLO、重试、队列、定时任务、降级、容灾或数据修复策略时创建。最低结构:关键用户旅程、SLO/错误预算、故障模式、超时重试、幂等与补偿、观测和告警、恢复/演练、Owner。
|
||||
|
||||
### SECURITY.md:何时创建
|
||||
|
||||
当存在统一信任边界、敏感数据、权限模型或外部暴露面时创建。最低结构:资产和角色、信任边界、认证授权、数据分类、密钥、审计、主要威胁与控制、验证命令、事件入口、Owner。
|
||||
|
||||
### PRODUCT_SENSE.md:何时创建
|
||||
|
||||
当多个功能反复需要相同产品判断、Agent 经常做出局部正确但产品错误的选择时创建。最低结构:目标用户、核心任务、优先级原则、明确非目标、取舍示例、正反例。具体行为仍归 OpenSpec。
|
||||
|
||||
### QUALITY_SCORE.md:何时创建
|
||||
|
||||
只有团队真的按固定维度定期评分并采取行动时创建。每项必须有定义、证据来源、当前分数、阈值、Owner、改进动作和复评日期;没有维护机制就不要创建。
|
||||
|
||||
## 14. 文档检查与持续清熵
|
||||
|
||||
### 每次变更
|
||||
|
||||
- 检查内部链接和引用路径。
|
||||
- OpenSpec 校验全部通过。
|
||||
- 生成文档可无差异再生。
|
||||
- 架构边界检查通过。
|
||||
- Change 完成后同步/归档 Specs,删除被替代的同义说明。
|
||||
|
||||
### 周期性 doc-gardening
|
||||
|
||||
Agent 定期生成候选清单,人只处理真正的判断点:
|
||||
|
||||
- 失效链接、孤儿文件和长期 TBD。
|
||||
- `last_verified` 过期且影响仍高的文档。
|
||||
- 与代码、测试或 Specs 冲突的声明。
|
||||
- 重复规则、重复事实和已被检查器取代的提醒。
|
||||
- 已完成的临时计划、报告和可丢弃运行证据。
|
||||
|
||||
清理优先级:删除 > 合并到权威位置 > 更新 > 新建索引。
|
||||
|
||||
### 黄金原则
|
||||
|
||||
把反复出现的 Review 意见和事故教训变成最靠近问题的自动化约束:测试、类型、Lint、Schema、结构检查或运行时保护。不要持续加长 AGENTS.md。
|
||||
|
||||
## 15. Day 0 到稳定期的落地顺序
|
||||
|
||||
1. Agent 生成最小可运行仓库、格式化、包管理、CI 和测试骨架。
|
||||
2. 建立短 `AGENTS.md`、可执行 `README.md` 和一页 `ARCHITECTURE.md`。
|
||||
3. 初始化 OpenSpec;第一个真实功能走 Explore → Propose → Review → Apply → Verify → Archive。
|
||||
4. 打通独立工作区启动、日志读取和最小 smoke 测试。
|
||||
5. 出现真实第三方时按外部契约模板创建资料。
|
||||
6. 出现重复工程失误时先加检查器,再补对应约束 ID。
|
||||
7. 只有触发条件成立时增加 Reliability、Security 等专项文档。
|
||||
8. 持续删除完成报告、重复事实和过期 Context,避免周期性大扫除。
|
||||
|
||||
## 16. 最低验收清单
|
||||
|
||||
- [ ] 新 Agent 只读 AGENTS.md 就能找到行为、架构、外部契约和验证入口。
|
||||
- [ ] README 的启动、测试、重置命令在干净工作区可执行。
|
||||
- [ ] ARCHITECTURE.md 的依赖方向有检查或明确人工门禁。
|
||||
- [ ] OpenSpec Requirement/Scenario 可观察、可判定。
|
||||
- [ ] 外部契约的版本、字段、签名、重试、幂等和验证证据齐全。
|
||||
- [ ] 每条工程约束有范围、规则、理由、检查、例外和 Owner。
|
||||
- [ ] Agent 能自行读取错误日志;有 UI 时能读取页面和网络失败。
|
||||
- [ ] 同一事实没有多个权威正文。
|
||||
- [ ] 文档链接、生成检查、Spec 校验和测试进入 CI。
|
||||
- [ ] 临时计划、状态和运行证据不会成为第二事实源。
|
||||
|
||||
## 17. 参考资料
|
||||
|
||||
### 第一方
|
||||
|
||||
- [OpenAI Harness Engineering](https://openai.com/index/harness-engineering/)
|
||||
- [OpenSpec Overview](https://openspec.dev/docs/overview)
|
||||
- [OpenSpec Writing Specs](https://openspec.dev/docs/writing-specs)
|
||||
- [OpenSpec Getting Started](https://openspec.dev/docs/getting-started)
|
||||
- [LongHorizon-Harness](https://github.com/AMAP-ML/LongHorizon-Harness)
|
||||
|
||||
### 补充实践
|
||||
|
||||
- [Augment:How to write good AGENTS.md files](https://www.augmentcode.com/blog/how-to-write-good-agents-dot-md-files)
|
||||
- [BetterClaw:AGENTS.md best practices](https://www.betterclaw.io/blog/agents-md-best-practices)
|
||||
- [What goes in AGENTS.md?](https://ro14nd.de/what-goes-in-agents-md/)
|
||||
- [Domain Expertise Is the New Agentic Coding Moat](https://www.developersdigest.tech/blog/domain-expertise-agentic-coding-moat)
|
||||
- [O’Reilly:How to write a good spec for AI agents](https://www.oreilly.com/radar/how-to-write-a-good-spec-for-ai-agents/)
|
||||
267
docs/feature-504-multi-view-audit-center/多视角审计中心功能总结.md
Normal file
267
docs/feature-504-multi-view-audit-center/多视角审计中心功能总结.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# 多视角审计中心功能总结
|
||||
|
||||
本文汇总 `build-multi-view-audit-center` 第一阶段已经交付的能力、边界和验收依据。实现以当前 Action/Resource Registry 和覆盖基线为准;旧账号、旧资产 operation log 只保留独立历史查询,不回填、不转换、不进入新审计中心。
|
||||
|
||||
## 1. 交付范围
|
||||
|
||||
- 统一不可变 `Audit Event + Event Resource`,记录真实操作者、入口、动作、结果、风险、多资源关系、资源快照、前后变化和跨步骤链路。
|
||||
- 平台提供全局事件、操作者、资源、request、correlation、资金、风险和 Integration Log 调查视角。
|
||||
- 代理和企业使用独立安全资源活动投影,只查看当前有权资源的允许业务结论。
|
||||
- Audit Event 与 Integration Log 按 `Asia/Shanghai` 完整自然日归档;整月完整性门禁通过后,可受控物理清理 PostgreSQL 上月在线数据。
|
||||
- 现行覆盖门禁已登记 692 个源码入口、219 个 Action Registry 动作、61 个 Resource Registry 类型、146 个已使用资源角色和 5 组敏感读取。
|
||||
|
||||
实现入口:`internal/infrastructure/audit/`、`internal/query/audit/`、`internal/query/integration/`、`internal/application/auditarchive/`、`internal/handler/admin/audit.go`、`internal/routes/audit.go`。完整逐入口决定见 [审计覆盖基线](../../.scratch/tech-global-audit/审计覆盖基线.md)。
|
||||
|
||||
## 2. 四类事实边界
|
||||
|
||||
| 事实 | 回答的问题 | 权威内容 | 明确不承担 |
|
||||
|---|---|---|---|
|
||||
| Access Log | 这次 HTTP 请求收发了什么、耗时多久 | method、path、query、status、duration、request/response body、request_id 等调试信息 | 不证明业务事实成功,不进入审计数据库查询,不上传本次冷归档 |
|
||||
| Audit Event | 谁从什么入口,对哪些资源做了什么,结果如何 | 操作者、动作、结果、风险、多资源关系、资源快照、before/after、request/correlation/parent | 不替代订单、钱包流水等业务账本,不替代外部交互或可靠投递事实 |
|
||||
| Integration Log | 系统与外部系统实际发生或未发生了哪次交互 | provider、direction、operation、result、attempt、外部状态、受控请求/响应摘要 | 不把投递成功或外呼成功伪装成内部业务成功,不承担人工恢复 |
|
||||
| Domain Ledger | 金额、状态、权益最终以什么为准 | 订单、支付、退款、充值、钱包流水、审批、套餐权益、佣金等业务表 | 不负责解释完整操作者上下文,不被 Audit Event 重算或覆盖 |
|
||||
|
||||
Outbox/Asynq/手动轮询是可靠副作用和任务运行事实:跨视角时间线可展示其摘要或引用,但仍保持独立生命周期。Outbox `delivered` 只表示投递事实,不表示消费者业务成功;手动轮询表继续保存进度和结果。
|
||||
|
||||
链路字段固定语义:`request_id` 关联一次 HTTP 请求;`correlation_id` 关联跨请求、Outbox、Asynq、回调和后续业务步骤;`parent_event_id` 表示直接因果;Integration 的 `trigger_series + attempt` 才表示技术尝试序列。
|
||||
|
||||
## 3. 核心字段字典
|
||||
|
||||
### 3.1 Audit Event
|
||||
|
||||
| 字段组 | 字段 | 语义 |
|
||||
|---|---|---|
|
||||
| 身份 | `event_id`、`occurred_at`、`created_at` | 稳定公开事件 ID、真实发生时间、持久化时间 |
|
||||
| 动作 | `category`、`action_code`、`action_name`、`summary` | 稳定动作类别/编码、中文名称快照和内部摘要 |
|
||||
| 操作者 | `actor_kind/id/name`、`actor_shop_id/name`、`actor_enterprise_id/name` | 事件发生时的真实人工、OpenAPI、系统任务、计划任务或外部系统身份快照 |
|
||||
| 入口 | `source`、`request_path/method`、`ip_address`、`user_agent` | admin/personal/openapi/worker/scheduler/callback 等来源及 HTTP 摘要 |
|
||||
| 范围 | `scope_type/id/name` | 平台、店铺、企业、个人等本次主要业务范围快照 |
|
||||
| 结果 | `result`、`risk_level`、`error_code`、`error_summary` | `success/failed/denied/partial/unknown`、风险和安全错误摘要 |
|
||||
| 链路 | `request_id`、`correlation_id`、`parent_event_id` | 请求、业务链路和直接父事件 |
|
||||
| 批量 | `batch_total`、`success_count`、`fail_count` | 批次根统计;子事件通过 parent/correlation 关联 |
|
||||
| 完整性 | `metadata`、`content_hash` | 有界安全业务参数和标准化内容 SHA-256 |
|
||||
|
||||
### 3.2 Event Resource
|
||||
|
||||
| 字段 | 语义 |
|
||||
|---|---|
|
||||
| `resource_type/id/key/display_name` | Registry 类型、可空内部 ID、事件时稳定业务 Key 和展示名 |
|
||||
| `relation` | `primary`、`affected`、`reference`;每个事件必须且只能按动作契约确定主要资源 |
|
||||
| `role` | `old_card/new_card/bound_card/entry_device/order/wallet_transaction` 等稳定业务角色 |
|
||||
| `identity_snapshot` | 事件发生时资源身份,不依赖当前业务表是否删除、更名、换号 |
|
||||
| `before_data/after_data` | 该资源本次真实涉及的字段,不使用事件级万能 JSON |
|
||||
| `subject_visibility` | `internal_only`、`subject_result`、`subject_detail` |
|
||||
| `subject_summary/subject_data` | 写入时生成的主体安全结论及 Registry 白名单字段 |
|
||||
| `sort_order/created_at` | 稳定展示顺序和落库时间 |
|
||||
|
||||
### 3.3 Integration Log
|
||||
|
||||
| 字段组 | 字段 | 语义 |
|
||||
|---|---|---|
|
||||
| 身份 | `integration_id`、`provider`、`direction`、`operation`、`external_id` | 稳定交互身份、提供方、方向、动作和外部业务标识 |
|
||||
| 资源 | `resource_type/id/key` | 本次外部交互的直接主资源;其他资源通过关联 Audit Event 展开 |
|
||||
| 触发 | `trigger_source/scene/series`、`attempt` | 来源、业务场景和显式技术尝试序列 |
|
||||
| 结果 | `result`、`http_status`、`provider_code/message`、`duration_ms`、`state_changed` | 原始结果、安全可读摘要、耗时和是否改变本地事实 |
|
||||
| 内容 | `request_summary`、`response_summary`、`metadata`、`content_hash` | 写入和返回前均经过白名单及凭据删除的结构化摘要 |
|
||||
| 链路 | `request_id`、`correlation_id`、`audit_event_id` | 与请求、业务链路和内部状态变化事件的稳定关联 |
|
||||
|
||||
派生结果类别固定为:`pending→processing`、`success→succeeded`、`unknown→indeterminate`、明确失败结果→`failed`、`ignored/merged/rate_limited/completed/cancelled→not_sent`。`completed` 不计外部请求成功。
|
||||
|
||||
### 3.4 留存与调查引用
|
||||
|
||||
| 结构 | 字段 | 语义 |
|
||||
|---|---|---|
|
||||
| `retention` | `online_from`、`archived_before`、`timezone` | 当前 PostgreSQL 在线窗口、已归档边界、`Asia/Shanghai` 时区 |
|
||||
| `investigation_refs` | `event_id`、`actor_ref`、`resource_refs[]`、`request_id`、`correlation_id`、`integration_refs[]` | 平台各调查视角唯一允许使用的稳定跳转引用 |
|
||||
| `tb_log_archive_run` | source/date/instance/schema/revision/status/is_final、range、object/manifest key、数量、字节、SHA-256、attempt、cleanup 时间 | 归档与清理轻量 ledger,不保存日志正文 |
|
||||
|
||||
## 4. 当前领域、资源与动作矩阵
|
||||
|
||||
下表按领域归并当前 Registry。动作列使用稳定编码前缀及代表动作;219 个动作的唯一权威清单是 `pkg/constants/audit.go` 与 `internal/infrastructure/audit/registry.go`,逐入口事务和 N/A 决定以覆盖基线为准。
|
||||
|
||||
| 领域 | 主要资源 | 已接入动作范围 | 权威事实/边界 |
|
||||
|---|---|---|---|
|
||||
| 账号与认证 | account、authentication、role、permission | `account.*`、`auth.*`、`role.*`、`permission.*` | 账号、角色权限和认证状态表;密码、Token、Cookie 不入审计 |
|
||||
| 店铺与企业 | shop、enterprise、企业卡/设备授权 | `shop.*`、`enterprise.*`、`enterprise_card.*`、`enterprise_device.*` | 店铺层级和当前有效授权关系 |
|
||||
| 个人客户 | personal_customer、手机号、OpenID、资产绑定 | `personal_customer.*` | 客户及绑定表;外部主体标识按 Registry 快照 |
|
||||
| IoT 卡 | iot_card、批次、分配记录、Integration | `iot_card.*`,含创建、删除、分配/回收、系列、实名、停复机、刷新、限速、Worker 同步 | 卡表保存本地事实;Gateway 尝试进入 Integration Log;限速审计记录谁操作、谁被限速、档位及外部结果 |
|
||||
| 设备与卡槽 | device、iot_card、device_sim_binding、分配记录 | `device.*`,含分配/回收、绑解绑、切当前卡、Wi-Fi、模式、重启、重置、Worker 观测 | 设备、卡及卡槽均为一等资源,多卡逐张关联 |
|
||||
| 换货 | exchange_order、旧新卡/设备、绑定、客户、钱包、权益 | `exchange.card.*`、`exchange.device.*` | 换货状态机、资产/钱包/权益等业务表;不压缩为单一资产标识 |
|
||||
| 套餐配置 | package_series、package、店铺授权、价格历史、批次 | `package_series.*`、`package.*`、`shop_series_grant.*`、`shop_package.*` | 套餐、授权和价格历史表 |
|
||||
| 套餐权益 | package_usage、订单、套餐、资产、退款 | `package_usage.*`,含激活、到期、扣减/重置、退款/资产失效、队列及迁移 | `tb_package_usage` 和流量业务表为权威 |
|
||||
| 订单与支付 | order、payment、资产、套餐、钱包/流水 | `order.*`、`payment.*` | 订单/支付状态、渠道流水;每次渠道尝试另写 Integration Log |
|
||||
| 退款 | refund、订单、审批、钱包/流水、佣金、权益、通知 | `refund.*` | 退款、钱包流水、佣金和权益事实同事务收口 |
|
||||
| 充值与钱包 | agent_recharge、recharge_order、agent/asset wallet、流水、预占 | `agent_recharge.*`、`asset_recharge.*`、`agent_wallet.*` | 金额和余额以钱包流水/业务单为权威;审计只解释操作者和动作 |
|
||||
| 佣金与提现 | commission_record、commission_withdrawal、钱包/流水 | `commission.*`、`commission_withdrawal.*` | 佣金、提现、钱包和流水状态机 |
|
||||
| 审批与企微 | approval_instance、业务单、Integration、Outbox | `approval.*`、`wecom.*` | 本地审批事实、企微交互和可靠终态分发各自保留 |
|
||||
| 系统与连接配置 | system_config、payment_config、carrier、wecom_application/scene | `system_config.*`、`payment_config.*`、`carrier.*`、`wecom.*` | 配置表权威;只记录 `credentials_configured` 等安全事实 |
|
||||
| 导入、批量与导出任务 | 各 task、批次资源、实际业务资源 | `*_import_task.*`、`asset_package_batch_order_task.*`、`order_package_invalidate_task.*`、`export_task.*` | 任务表保存进度;实际变化资源另有子事件;审计中心本身不导出 |
|
||||
| 通知 | notification、read/cleanup batch、业务目标 | `notification.deliver/read/read_all/cleanup/cleanup_item` | 通知正文和已读状态以通知表为准 |
|
||||
| 轮询与监控 | polling_config/concurrency/alert/manual_trigger、卡 | `polling_config.*`、`polling_concurrency.*`、`polling_alert.*`、`polling_manual_trigger.*` | 人工触发写 Audit;每次 Gateway 尝试写 Integration;手动任务表继续保存运行事实 |
|
||||
| 可靠事件与留存 | outbox_event、log_archive_month | `outbox.*`、`audit.retention_cleanup` | Outbox 投递、归档 ledger、对象存储事实保持独立 |
|
||||
|
||||
普通列表、详情、统计和装配入口为 N/A;Registry 标记的敏感读取在返回结果前写审计,写入失败则不返回敏感结果。
|
||||
|
||||
## 5. 各资源身份快照
|
||||
|
||||
| 资源 | 最小可读快照 |
|
||||
|---|---|
|
||||
| 账号/角色/权限 | ID、用户名、手机号、账号类型、店铺/企业、企微身份;角色 ID/名称/类型;权限 code/name |
|
||||
| 店铺/企业 | ID、编码、名称、上级或 owner shop、层级 |
|
||||
| 个人客户 | ID、昵称、手机号、微信主体标识及资产绑定标识 |
|
||||
| IoT 卡 | ID、ICCID、VirtualNo、MSISDN、运营商、店铺、系列、generation |
|
||||
| 设备 | ID、VirtualNo、IMEI、SN、名称/型号、店铺、系列、generation |
|
||||
| 设备卡槽 | binding ID、设备标识、slot_position、卡 ICCID/VirtualNo、is_current |
|
||||
| 分配/换货 | 分配单号或换货单号、资产类型/ID/完整标识、来源/目标主体、旧新资产、店铺、状态 |
|
||||
| 套餐/权益 | 套餐/系列 ID、编码、名称、期限、价格、状态;权益 ID、订单、资产、generation、激活/到期时间 |
|
||||
| 订单/支付 | 订单/支付 ID 和单号、买家、资产、套餐、金额、支付方式/状态、渠道交易号、配置 ID |
|
||||
| 退款/充值 | 业务单号、订单/资产/店铺、申请/批准金额、审批、支付方式、状态 |
|
||||
| 钱包/流水/预占 | 钱包 ID/类型、店铺或资产、币种、流水/预占 ID、reference、amount、balance before/after |
|
||||
| 佣金/提现 | 记录或申请 ID、店铺、订单、系列、金额、状态、结算周期 |
|
||||
| 审批 | 实例 ID、业务类型/ID、提交人、provider、external ref、correlation、状态 |
|
||||
| 配置 | 配置 Key/模块或 ID/name/provider、状态、`credentials_configured`;不保存密钥正文 |
|
||||
| 任务/通知/轮询 | 任务 ID/单号、文件名、目标、操作者、统计;通知 event/接收人/类别/ref;轮询配置/触发 ID、资产、任务类型 |
|
||||
| Integration/Outbox | integration ID/provider/operation/resource/external ID;event ID/type/aggregate |
|
||||
|
||||
手机号、IP、ICCID、VirtualNo、金额和交易号对平台按已存业务值完整展示;密码、验证码、Token、Secret、私钥、支付密钥、回调凭据、Authorization、Cookie、签名 URL 和完整第三方原始正文从持久化前删除。
|
||||
|
||||
## 6. 查询视角与 DTO
|
||||
|
||||
| 视角/API | 请求 DTO 重点 | 响应 DTO 重点 |
|
||||
|---|---|---|
|
||||
| `GET /audit/events` | 时间、action/category、actor、source/result/risk、scope、resource、request/correlation、分页 | `EventPage{total,page,page_size,items[],retention}` |
|
||||
| `GET /audit/events/{event_id}` | 稳定 event ID | `EventDetail`:完整 `EventView`、全部 `ResourceView`、retention |
|
||||
| `GET /audit/actors/{kind}/{id}/events` | actor、action/result/risk/resource、时间、分页 | 与事件列表相同的稳定分页投影 |
|
||||
| `GET /audit/resources/search` | 首批 `iot_card/device/shop/order/refund`、精确 keyword、分页 | `ResourceCandidate{type,id,key,display_name,identity_snapshot,historical}` |
|
||||
| `GET /audit/resources/{type}/{id}/timeline` | Registry type、内部稳定 ID、时间、action/result、分页 | 资源以任意 relation 参与的 `EventPage` |
|
||||
| `GET /audit/requests/{request_id}/timeline` | 稳定 request ID | `LinkTimeline`、Access Log 检索 ID、按 `record_source` 分组节点 |
|
||||
| `GET /audit/correlations/{correlation_id}/timeline` | 稳定 correlation ID | Audit、Integration、Outbox、任务摘要和 Domain Ledger 引用节点 |
|
||||
| `GET /audit/finance/timeline` | shop/wallet/order/payment/refund/recharge/approval/trade/actor/correlation/time 任一稳定条件 | `FinanceTimelineNode`、amount/balance、权威表字段、调查引用 |
|
||||
| `GET /audit/risks/overview` | 最长 31 天时间范围、risk/result/action/source | signals、risks、results、actions、sources、trend、retention |
|
||||
| `GET /audit/risks/events` | 总览同源筛选及分页 | 风险范围内的 `EventView` 分页 |
|
||||
| `GET /audit/integrations/overview` | 时间、bucket 及 Integration 组合筛选 | 总量、异常/unknown/陈旧 pending/状态变化、平均/P95、分布、趋势、retention |
|
||||
| `GET /audit/integrations` | provider/direction/operation/result/category/resource/series/linkage 等组合筛选、分页 | `ListItem` 稳定编码/中文名、资源、结果、耗时、链路 |
|
||||
| `GET /audit/integrations/{integration_id}` | 稳定 integration ID | identity/resource/trigger/result/content/linkage/timestamps/attempts/fidelity/retention |
|
||||
| `GET /agent/resource-activities/{type}/{identifier}` | 业务稳定 identifier、时间、分页;范围来自认证上下文 | 资源摘要、`SubjectActivity[]`、retention |
|
||||
| `GET /enterprise/resource-activities/{type}/{identifier}` | 企业仅支持当前有效授权卡/设备 | 与代理相同安全 DTO,不含内部调查引用 |
|
||||
|
||||
所有完整路径均以 `/api/admin` 开头。`request_id/correlation_id` 生命周期、15 条接口入参与返回、页面字段来源和降级规则统一见[审计链路与前端接入指南](跨视角调查与前端导航契约.md);专题说明见[平台基础审计调查接口](平台基础审计调查接口.md)、[外部集成调查接口](外部集成调查接口.md)和[主体资源活动接口](主体资源活动接口.md)。OpenAPI 权威制品为 `docs/admin-openapi.yaml` 和运行时生成的 `logs/openapi.yaml`。
|
||||
|
||||
平台调查节点统一使用 `investigation_refs`。字段不存在时隐藏跳转;只有稳定 Key、没有内部 ID 时先精确资源搜索,零命中或多命中均不自动选择,禁止按名称、时间、中文描述或编号前缀猜测关系。
|
||||
|
||||
## 7. 平台与主体可见性
|
||||
|
||||
| 身份 | 可见能力 | 不可见能力 |
|
||||
|---|---|---|
|
||||
| 超级管理员/平台账号 | 全部 `/api/admin/audit/*` 在线数据;完整已存业务字段、操作者、风险、before/after、Integration 安全摘要 | 系统安全凭据、归档对象读取、修改/删除/导出/恢复/风险处置 |
|
||||
| 代理 | 自己及下级店铺当前有权资源的 `subject_result/subject_detail` | 平台 actor、内部原因/备注/风险/before/after、event ID、request/correlation、Integration 内容、`internal_only` |
|
||||
| 企业 | 当前有效授权卡/设备的安全活动;授权撤销后立即不可读 | 平台接口、企业 ID 伪造范围、分配/换货独立内部调查、所有内部字段 |
|
||||
| 个人客户 | 不开放本审计中心调查接口 | 平台及主体资源活动接口 |
|
||||
|
||||
代理/企业查询先验证当前资源归属,再读主体投影;不存在、不支持、越权或授权撤销统一返回“无权限操作该资源或资源不存在”,不泄露资源或事件是否存在。`subject_data` 必须在写入时按 Registry 白名单生成,Query 不从内部 before/after 临时删字段拼装。
|
||||
|
||||
## 8. 代表性业务样例
|
||||
|
||||
### 多卡设备
|
||||
|
||||
操作设备第二卡槽的卡时,目标卡是 primary/affected,入口设备是 reference,绑定关系保存 `slot_position=2` 和当时 `is_current`。切换当前卡同时关联设备、旧卡、新卡和两个 binding,分别写各自前后状态,因此设备、两张卡和绑定关系时间线都能定位该事件。
|
||||
|
||||
### 换货
|
||||
|
||||
卡换货按角色关联换货单、旧卡、新卡、旧新钱包、流水、套餐权益和客户绑定,并分别保存旧新卡 ICCID/VirtualNo。设备换货保存旧新设备 VirtualNo/IMEI/SN,并逐张关联实际涉及的绑定卡和卡槽;不会用一个 `asset_identifier` 掩盖多资源关系。
|
||||
|
||||
### 资金
|
||||
|
||||
订单钱包扣款、退款回充、充值入账、预占/释放、佣金和提现的成功审计与钱包、唯一流水、业务单及必要 Outbox 同一 GORM 事务;审计失败则业务回滚。资金时间线返回 `amount_authority`,金额与余额冲突时以钱包流水及业务表为准,不修改历史 Audit Event。
|
||||
|
||||
### 批量
|
||||
|
||||
批量分配、批量配置、导入购包等写一条根事件,记录 total/success/fail 和 `success/partial/failed`;每个实际变化或已识别失败资源写子事件,共享 correlation 并以根 event 为 parent。未命中、未处理和幂等无变化项不伪造成功子事件。
|
||||
|
||||
## 9. 异常闭环与事务策略
|
||||
|
||||
| 场景 | 处理 |
|
||||
|---|---|
|
||||
| 关键成功或 partial 且改变业务事实 | Audit 与 Domain Ledger/必要 Outbox 同事务;审计失败整体回滚 |
|
||||
| 已定位主要资源的业务拒绝 | 业务不落地,随后用独立短事务写 `denied` |
|
||||
| 已定位主要资源后的执行失败 | 保留原业务错误,独立短事务写 `failed` |
|
||||
| 失败审计二次失败 | 不覆盖原错误;写 critical 日志并递增监控计数 |
|
||||
| 参数解析前失败或无法定位主要资源 | 不造无资源 Audit Event,只进入 Access/Security Log |
|
||||
| 外部交互未改变内部事实 | 只写 Integration Log |
|
||||
| Callback/Worker 改变内部事实 | 保留 Integration/任务事实,并以真实 external/system actor 写 Audit Event |
|
||||
| 普通查询 | N/A,不写 Audit Event |
|
||||
| Registry 敏感读取 | 返回敏感结果前写 Audit;失败关闭,不返回结果 |
|
||||
|
||||
Writer 对未知 action、未知 resource/role、缺少主要资源、资源关系不完整、安全凭据未清理和不符合事务要求的动作保持 fail-closed。业务修正通过新的受控动作产生新事件,不更新旧事件。
|
||||
|
||||
## 10. 每日归档、月度清理与在线窗口
|
||||
|
||||
1. 每日任务按 `Asia/Shanghai` 前一完整自然日半开区间归档。
|
||||
2. Audit 对象使用 UTF-8 JSONL+gzip,每行一个事件及完整 `resources[]`;Integration 每行一条结构化持久化事实。
|
||||
3. 每个对象有 manifest、SHA-256、对象 metadata 和 `tb_log_archive_run` ledger;内容变化创建新 revision,不覆盖旧对象。
|
||||
4. 月初先完成上月最后一天归档,并用数据库当前内容形成 Integration 最终 revision;任何 pending、缺日、数量/hash/对象不一致都阻止整月清理。
|
||||
5. 门禁通过后,Retention Worker 以 1000 行有界批次按 Event Resource → Audit Event → Integration Log 顺序物理删除目标月 PostgreSQL 数据,并通过 cleanup 时间断点续跑。
|
||||
6. 清理任务在当前月写 `audit.retention_cleanup`,不写回被清理月份;对象和 manifest 长期保留,不被该任务删除。
|
||||
|
||||
默认生产开关为 `JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false`。灰度、仿真、SQL 观测、告警和启停步骤见[审计归档灰度操作手册](归档灰度操作手册.md)。
|
||||
|
||||
所有在线调查 DTO 返回 `retention`。显式范围早于在线边界或跨越边界时返回 `CodeAuditDataArchived`;ID-only 详情在线库不存在仍按资源不存在处理。接口不扫描对象存储,也不返回误导性的成功空结果或部分历史。
|
||||
|
||||
## 11. 发布、回滚与监控
|
||||
|
||||
发布前要求迁移版本 `205`、`dirty=false`,旧 Writer 生产调用为 0,Registry/覆盖门禁通过,两份 OpenAPI 可生成,生产物理清理开关保持关闭。
|
||||
|
||||
回滚只影响后续流量:保留当前在线 Audit、Integration、Domain Ledger、Outbox、归档 ledger 和已校验对象;不得恢复旧账号/资产 Writer,不得用 migration down 删除已有事实,不得从对象存储回填 PostgreSQL 伪造在线历史。错误事实通过前向业务动作修正。
|
||||
|
||||
| 监控项 | 阈值 |
|
||||
|---|---|
|
||||
| 关键成功审计写入失败 | 任一失败即 critical |
|
||||
| failed/denied 短事务二次失败 | 增量必须为 0 |
|
||||
| 未注册 action/resource/role | 必须为 0 |
|
||||
| 安全凭据落库或响应命中 | 必须为 0 |
|
||||
| 批量根子计数、parent/correlation 差异 | 必须为 0 |
|
||||
| 旧 Writer 生产写调用 | 必须为 0 |
|
||||
| 数据库受控查询 | < 50ms |
|
||||
| 审计读接口 | P95 < 200ms,P99 < 500ms;发布后用既有 Access Log 观测 |
|
||||
| 每日归档缺失/失败、对象或 manifest 不一致、月度非 final | critical,阻止清理 |
|
||||
|
||||
## 12. 明确未实现项
|
||||
|
||||
- 细粒度平台审计权限码和平台数据行过滤。
|
||||
- 面向用户的审计导出、归档下载、对象存储历史查询或恢复。
|
||||
- PostgreSQL 冷热联合查询、归档回填和月分区。
|
||||
- 审计事件修改、删除、专用纠错 API;Retention Worker 的受控整月清理是唯一删除例外。
|
||||
- Integration Log 重试、补偿、结果确认、外部单号绑定、人工恢复、修改或删除。
|
||||
- 风险处置工单、自动封禁和自动恢复。
|
||||
- 任意关系图、任意 JSONB/JSONPath 搜索、JSONB GIN 和 Redis 查询缓存。
|
||||
- 将旧 operation log 回填、转换或拼入新审计中心。
|
||||
- 从 Access Log、相近时间、相似资源或中文描述猜测业务链路和技术重试。
|
||||
|
||||
## 13. 验收依据
|
||||
|
||||
- 实现:`internal/model/audit_event.go`、`internal/model/integration_log.go`、`internal/model/log_archive_run.go`、`internal/infrastructure/audit/`、`internal/query/audit/`、`internal/query/integration/`、`internal/application/auditarchive/`。
|
||||
- API:`internal/handler/admin/audit.go`、`internal/routes/audit.go`、`internal/model/dto/audit_dto.go`、`docs/admin-openapi.yaml`、`logs/openapi.yaml`。
|
||||
- 覆盖:`.scratch/tech-global-audit/审计覆盖基线.md`、`.scratch/tech-global-audit/审计覆盖清单.json`、`cmd/audit-coverage`。
|
||||
- 发布与留存:[审计归档灰度操作手册](归档灰度操作手册.md)。测试环境完整月仿真为 `2001-02`:56/56 归档成功,Integration 28/28 final,清理后三张目标日志表归零,6 条边界哨兵和对象归档全部保留。
|
||||
- 性能只读观测:事件列表 0.315ms、资源时间线 0.116ms、Integration 列表 0.186ms、风险聚合 0.123ms;API P95/P99 使用既有 9.4 证据及发布后 Access Log 阈值,不执行接口压测。
|
||||
- 本 Change 按明确约束不新增、修改或运行自动化测试;交付验证使用 OpenAPI 生成、覆盖静态门禁、编译、LSP、迁移/数据核对、只读性能观测和文档一致性检查。
|
||||
|
||||
## 14. 最终验收结论
|
||||
|
||||
2026-08-07 按 proposal、design、8 份 delta spec 和 77 项 tasks 完成最终核对:
|
||||
|
||||
| 门禁 | 最终证据 | 结论 |
|
||||
|---|---|---|
|
||||
| 静态覆盖 | `cmd/audit-coverage` 重新生成 692 条当前入口;Action/Resource Registry、敏感读取、N/A 和旧 Writer 白名单无未登记项 | PASS |
|
||||
| LSP 与构建 | 变更 Go 文件 `gopls check` 无诊断;`go build ./...` 退出码 0 | PASS |
|
||||
| 迁移 | `.env.local` 测试库只读核对版本 205、未标 dirty;无事实隔离 schema 已完成 `000199`~`000205` up/down,现有事实库未执行破坏性 down | PASS |
|
||||
| OpenAPI | `cmd/gendocs` 与运行时生成成功;两份制品各含 15 条平台调查及代理/企业活动路径且契约一致;固定请求/响应枚举已生成真实 `enum`,审计相关公开 schema 无缺失字段说明,RouteSpec 已写明业务页字段、调查引用和通知目标映射 | PASS |
|
||||
| 前端接入文档 | [审计链路与前端接入指南](跨视角调查与前端导航契约.md) 已覆盖 request/correlation/parent/integration/series 生命周期、数据库落点、自动与人工轮询边界、15 条接口全部入参与主要返回字段、现有业务页面入口矩阵和完整调用链;OpenAPI 48 个请求参数名无遗漏 | PASS |
|
||||
| 数据库与 API 性能 | 四条代表性数据库查询均小于 1ms且低于 50ms;API P95/P99 复用 9.4 既有证据,发布后以 Access Log 按 200ms/500ms 阈值持续观测 | PASS |
|
||||
| 身份与凭据 | 平台、代理店铺层级、企业有效授权和 `internal_only` 边界已核对;数据库/响应凭据抽样为 0 命中,无审计导出、归档读取/恢复或业务删除路由 | PASS |
|
||||
| 事务与业务链路 | 关键成功同事务、失败短事务保留原错、批量根子计数、真实 actor、request/correlation/parent/series 及四类事实边界证据已在 11.4 收口 | PASS |
|
||||
| 旧写切换 | 旧账号/资产 Writer 生产写为 0;旧表、旧资产历史查询和手动轮询运行 ledger 按契约保留 | PASS |
|
||||
| 归档与清理 | `2001-02` 共 56/56 归档成功、Integration 28/28 final;dry-run 后测试库目标月三表归零,6 条边界哨兵、56 条断点及全部对象/manifest 保留,生产清理开关关闭 | PASS |
|
||||
| 制品一致性 | `git diff --check` 无错误;`openspec validate build-multi-view-audit-center --strict` PASS | PASS |
|
||||
|
||||
验收范围内已知错误为零,77 项任务全部完成,Change 可归档。未实现项仍严格保持第 12 节边界,不因验收扩展。
|
||||
@@ -47,7 +47,7 @@
|
||||
| 店铺主钱包流水 | path `shop_id`、资产类型与 ID | 资金视角使用 `shop_id`;资产 ID 存在时使用对应资源时间线 |
|
||||
| 资产钱包流水 | 上层 `wallet_id` | 资金视角使用 `wallet_id`;不解析业务编号前缀猜测资源 |
|
||||
|
||||
资金视角已交付;缺失的支付、退款、钱包等关联由服务端 Query 解析。完整逐页面映射和调用链见[跨视角调查与前端导航契约](跨视角调查与前端导航契约.md)。
|
||||
资金视角已交付;缺失的支付、退款、钱包等关联由服务端 Query 解析。完整 ID 生命周期、逐接口字段、页面映射和调用链见[审计链路与前端接入指南](跨视角调查与前端导航契约.md)。
|
||||
|
||||
## 统一调查引用
|
||||
|
||||
|
||||
166
docs/feature-504-multi-view-audit-center/归档灰度操作手册.md
Normal file
166
docs/feature-504-multi-view-audit-center/归档灰度操作手册.md
Normal 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 记录缺失、失败或仍为 running:critical。
|
||||
- running 持续超过 3 小时、`attempt_count > 1` 或对象/manifest 复核失败:critical。
|
||||
- 有数据时压缩率不在 `(0, 1]`、SHA-256 为空或对象大小为零:critical。
|
||||
- 月度最终复核后任一 Integration 记录 `is_final=false` 或存在 pending:critical。
|
||||
- 只归档模式出现任一 `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 < 200ms,P99 < 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 最终 revision:28/28;故障重试后最大 revision=2、最大 attempt_count=2。
|
||||
- 压缩率:0.5047;dry-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 未删除。
|
||||
@@ -1,8 +1,326 @@
|
||||
# 跨视角调查与前端导航契约
|
||||
# 审计链路与前端接入指南
|
||||
|
||||
本文对应 `build-multi-view-audit-center` 任务 9.5,冻结 request、correlation、资金和风险调查接口,以及现有业务页面进入审计中心的第一跳。所有字段路径均位于统一响应的 `response.data` 下;列表字段位于 `items[]`。本文只定义前端调用契约,不包含前端页面实现。
|
||||
本文对应 `build-multi-view-audit-center` 任务 9.5,作为审计链路和前端接入的单一说明入口。它回答以下问题:链路 ID 从哪里产生、存到哪里、不同入口为什么有的没有 `request_id`、15 条审计接口如何调用、每个入参与返回字段表示什么,以及前端应在哪些现有业务页面增加入口。
|
||||
|
||||
## 跨视角只读接口
|
||||
所有接口都使用统一响应 `{code,msg,data,timestamp}`;本文中的返回字段默认位于 `response.data`,列表项默认位于 `response.data.items[]`。本文定义调用契约,不包含前端页面实现;机器可读类型、枚举和约束以 `docs/admin-openapi.yaml` 为准。
|
||||
|
||||
## 一、先理解五个稳定 ID
|
||||
|
||||
| 字段 | 表示什么 | 谁产生 | 前端能否产生 | 主要用途 |
|
||||
|---|---|---|---|---|
|
||||
| `request_id` | 一次 HTTP 请求 | 请求携带 `X-Request-ID` 时沿用;否则 Fiber 中间件生成 UUID | 业务页面不应为了审计查询临时生成;HTTP 客户端可统一传入自己的全局唯一 ID | 把 Access Log、同一次 HTTP 内的 Audit Event、Integration Log 和 Outbox 串起来 |
|
||||
| `correlation_id` | 一条可跨 HTTP、Outbox、Asynq、Callback 和外部交互的业务链路 | HTTP 起点默认等于 `request_id`;进入异步或具体业务后可改为订单号、支付号、Integration ID、任务 ID 等稳定链路值 | 不得根据时间、资源或中文描述推断 | 查询完整业务链路;它不表示技术重试 |
|
||||
| `parent_event_id` | 一个审计事件的直接父事件 | 批量根事件、异步派生或消费者在创建子事件时传播 | 不生成 | 表示直接因果和批量根子关系,不替代 correlation |
|
||||
| `integration_id` | 一次外部交互尝试的稳定 ID | Integration Log Writer 生成或调用方提供 | 不生成 | 查询单次外部交互详情,也是轮询链路的重要入口 |
|
||||
| `trigger_series` + `attempt` | 同一外部操作的显式技术尝试序列和序号 | 可重试的 Integration 调用方 | 不生成 | 判断第几次重试;没有 series 时只展示单次记录,禁止猜测重试 |
|
||||
|
||||
### `request_id` 的实际生命周期
|
||||
|
||||
1. 浏览器、开放接口调用方或外部回调发起 HTTP 请求。调用方如已提供 `X-Request-ID`,服务端沿用;否则服务端生成 UUID。
|
||||
2. 服务端把同一个值写入响应头 `X-Request-ID`,浏览器可读取该响应头用于问题反馈。
|
||||
3. Access Log 把它记录为 `request_id`,同时审计上下文在 HTTP 起点设置 `request_id`,并默认设置 `correlation_id=request_id`。
|
||||
4. 当前请求内发生业务变更时,Audit Writer 将其写入 `tb_audit_event.request_id`;发生外部交互时可写入 `tb_integration_log.request_id`;创建可靠事件时可写入 `tb_outbox_event.request_id`。
|
||||
5. 前端只有在事件或 Integration 返回了该值,或运维人员从响应头/Access Log 得到该值时,才调用请求时间线。普通资源详情页不需要先取得 `request_id`。
|
||||
|
||||
不是每个 HTTP 请求都会产生 Audit Event。例如普通列表查询只有 Access Log,没有业务审计事件;此时即使响应头有 `X-Request-ID`,请求时间线也可能没有业务节点。
|
||||
|
||||
### `request_id` 在哪里落库
|
||||
|
||||
| 事实 | 存储位置 | `request_id` 字段 | `correlation_id` 字段 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| HTTP 调试事实 | Access Log 文件 | JSON 字段 `request_id` | 当前访问日志不承担业务 correlation 查询 | 用于按请求排查原始 HTTP;审计时间线不会扫描日志文件 |
|
||||
| Audit Event | `tb_audit_event` | `request_id`,非空列但允许保存空字符串 | `correlation_id`,非空列但允许保存空字符串 | 解释谁对什么资源做了什么 |
|
||||
| Integration Log | `tb_integration_log` | `request_id`,可空 | `correlation_id`,可空 | 解释调用了哪个外部系统、结果和尝试序列 |
|
||||
| Outbox | `tb_outbox_event` | `request_id`,允许空字符串 | `correlation_id`,允许空字符串 | 可靠投递事实;投递成功不等于业务成功 |
|
||||
| Asynq | 任务 payload 或 Audit Event 的任务资源引用 | 按任务类型选择性携带 | 按任务类型选择性携带 | Redis 队列不是审计查询数据源;查询只展示已持久化引用,不扫描历史队列 |
|
||||
| 手动轮询运行记录 | `tb_polling_manual_trigger_log` | 没有该字段 | 没有该字段 | 只承担进度、结果、触发人和卡列表;人工触发命令另写 Audit Event |
|
||||
|
||||
## 二、轮询为什么通常没有 `request_id`
|
||||
|
||||
普通自动轮询由 Scheduler/Asynq Worker 触发,不是 HTTP 请求,因此没有 `request_id` 是正确语义:
|
||||
|
||||
1. Worker 调用 Gateway 前创建 Integration Log,生成 `integration_id`。
|
||||
2. 普通轮询的 `tb_integration_log.request_id` 保持空;`trigger_series` 和 `correlation_id` 通常使用该 `integration_id`。
|
||||
3. Gateway 返回后,如果观测结果实际改变卡、设备、套餐或网络状态,Worker 才写 Audit Event。
|
||||
4. 该 Audit Event 的 `request_id` 仍为空,`correlation_id` 使用 Integration ID、Asynq Task ID 或稳定任务标识,从而与外部尝试和资源时间线关联。
|
||||
5. 前端看到 `request_id` 为空时隐藏“请求链路”;`correlation_id` 非空时显示“业务链路”;无论两者是否存在,都可以继续使用 `integration_id` 或资源引用。
|
||||
|
||||
人工轮询需要区分两个事实:
|
||||
|
||||
- 管理员点击“手动触发”的 HTTP 命令会产生 Audit Event,因此该“谁发起了轮询”事件有 `request_id`。
|
||||
- 随后真正执行轮询的是 Worker。当前手动队列只携带卡 ID,执行阶段没有原 HTTP `request_id`;Gateway 尝试和实际观测变化按 Worker 自身的 Integration ID/correlation 串联。
|
||||
- `tb_polling_manual_trigger_log` 继续显示进度和结果;Audit Event 解释谁触发/取消;Integration Log 解释 Gateway 是否被调用以及调用结果。三类事实不能合并成一张记录。
|
||||
|
||||
## 三、前端选择入口的顺序
|
||||
|
||||
前端不应把 `request_id` 当作所有审计入口的必填条件。统一按以下顺序选择:
|
||||
|
||||
1. 业务页面已有内部 ID:平台直接调用资源时间线。
|
||||
2. 代理或企业页面已有 ICCID、VirtualNo、分配单号、换货单号等业务 identifier:调用主体资源活动接口。
|
||||
3. 订单、退款、充值、钱包或店铺页面:直接以任一稳定业务 ID 调用资金时间线,关联 ID 由服务端补全。
|
||||
4. 审计节点返回 `investigation_refs`:按引用字段跳转事件、操作者、资源、请求、correlation 或 Integration 视角。
|
||||
5. 只有 `resource_key` 没有内部 ID:先调用资源精确搜索,零命中或多命中时让用户选择,不自动猜测。
|
||||
6. 必需字段不存在:隐藏入口;不得用名称、中文说明、相近时间、编号前缀或第三方流水猜关系。
|
||||
|
||||
## 四、15 条接口总览
|
||||
|
||||
| 接口 | 何时调用 | 必填入参 | 主要返回 |
|
||||
|---|---|---|---|
|
||||
| `GET /api/admin/audit/events` | 平台审计中心全局列表或其他节点带筛选跳转 | 无 | `EventPage` |
|
||||
| `GET /api/admin/audit/events/{event_id}` | 查看一个稳定审计事件 | `event_id` | `EventDetail` |
|
||||
| `GET /api/admin/audit/actors/{kind}/{id}/events` | 查看某个操作者行为 | `kind`、`id` | `EventPage` |
|
||||
| `GET /api/admin/audit/resources/search` | 只有业务 Key、没有内部资源 ID | `resource_type`、`keyword` | `ResourceSearchPage` |
|
||||
| `GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline` | 平台业务页面或资源引用进入审计 | `resource_type`、`resource_id` | `EventPage` |
|
||||
| `GET /api/admin/audit/requests/{request_id}/timeline` | 已有真实 request ID 时调查一次 HTTP | `request_id` | `LinkTimeline` |
|
||||
| `GET /api/admin/audit/correlations/{correlation_id}/timeline` | 调查跨请求、异步和外部业务链路 | `correlation_id` | `LinkTimeline` |
|
||||
| `GET /api/admin/audit/finance/timeline` | 从订单、退款、充值、钱包、店铺等进入资金调查 | 至少一个稳定资金条件 | `FinanceTimelinePage` |
|
||||
| `GET /api/admin/audit/risks/overview` | 平台风险中心总览 | 无;时间不传时使用在线窗口 | `RiskOverview` |
|
||||
| `GET /api/admin/audit/risks/events` | 从风险分桶查看明细 | 无;分页和筛选可选 | `RiskEventPage` |
|
||||
| `GET /api/admin/audit/integrations/overview` | 平台 Integration 调查总览 | 无;时间不传时使用在线窗口 | `IntegrationOverview` |
|
||||
| `GET /api/admin/audit/integrations` | 外部交互列表和组合筛选 | 无 | `IntegrationListPage` |
|
||||
| `GET /api/admin/audit/integrations/{integration_id}` | 查看单次外部交互和尝试序列 | `integration_id` | `IntegrationDetailResponse` |
|
||||
| `GET /api/admin/agent/resource-activities/{resource_type}/{identifier}` | 代理查看授权范围内资源活动 | `resource_type`、`identifier` | `SubjectActivityPage` |
|
||||
| `GET /api/admin/enterprise/resource-activities/{resource_type}/{identifier}` | 企业查看当前有效授权卡或设备活动 | `resource_type`、`identifier` | `SubjectActivityPage` |
|
||||
|
||||
平台 `/audit/*` 接口仅允许超级管理员和平台账号访问;代理、企业必须使用各自的主体活动接口。全部为 GET,不提供修改、删除、导出、恢复、重试、补偿或风险处置。
|
||||
|
||||
## 五、请求参数字典
|
||||
|
||||
### 通用时间与分页
|
||||
|
||||
| 字段 | 类型 | 是否必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| `created_from` | RFC3339 字符串 | 否 | 起始时间,包含该时刻;未传时从在线留存窗口开始 |
|
||||
| `created_to` | RFC3339 字符串 | 否 | 结束时间,不包含该时刻;未传时为当前时间 |
|
||||
| `page` | int | 否 | 页码,默认 1 |
|
||||
| `page_size` | int | 否 | 每页数量,默认 20,最大 100 |
|
||||
|
||||
显式时间早于 `retention.online_from` 或跨越在线边界时返回“数据已归档”稳定错误,不会从对象存储读取部分结果。Integration 和风险查询最长连续 31 天。
|
||||
|
||||
### 全局事件 `GET /audit/events`
|
||||
|
||||
| 字段 | 类型/枚举 | 来源与用途 |
|
||||
|---|---|---|
|
||||
| `action` | string | 稳定动作编码,使用返回的 `action_code` |
|
||||
| `category` | `configuration/reliability/asset/security/identity/business` | 动作类别 |
|
||||
| `actor_kind` | `account/personal_customer/openapi/system_task/scheduled_job/external_system` | 操作者类型 |
|
||||
| `actor_id` | string | 与 actor_kind 共同定位操作者 |
|
||||
| `source` | `admin_api/personal_api/openapi/worker/scheduler/callback` | 操作入口 |
|
||||
| `result` | `success/failed/denied/partial/unknown` | 业务结果 |
|
||||
| `risk` | `low/normal/high/critical` | 风险等级 |
|
||||
| `scope_type` | `platform/shop/personal_customer` | 业务范围类型 |
|
||||
| `scope_id` | string | 业务范围稳定 ID |
|
||||
| `resource_type` | Registry 类型 | 资源类型 |
|
||||
| `resource_id` | string | 资源内部稳定 ID |
|
||||
| `resource_key` | string | 资源业务稳定 Key |
|
||||
| `request_id` | string | 可选精确筛选;来自真实 HTTP 请求,不是必填入口 |
|
||||
| `correlation_id` | string | 可选精确筛选业务链路 |
|
||||
| `created_from/to/page/page_size` | 通用字段 | 时间和分页 |
|
||||
|
||||
### 事件详情、操作者和资源
|
||||
|
||||
| 接口 | 参数 | 说明 |
|
||||
|---|---|---|
|
||||
| `/audit/events/{event_id}` | path `event_id` | 来自事件列表或 `investigation_refs.event_id` |
|
||||
| `/audit/actors/{kind}/{id}/events` | path `kind/id`;query `action/result/risk/resource_type/resource_id/created_from/created_to/page/page_size` | kind/id 来自 `actor_ref`;action 使用返回的稳定编码 |
|
||||
| `/audit/resources/search` | query `resource_type/keyword/page/page_size` | resource_type 仅 `iot_card/device/shop/order/refund`;keyword 为精确业务标识 |
|
||||
| `/audit/resources/{resource_type}/{resource_id}/timeline` | path `resource_type/resource_id`;query `created_from/created_to/action/result/page/page_size` | path 来自业务页面内部 ID、搜索结果或 resource_refs |
|
||||
|
||||
资源搜索 keyword 规则:卡支持 ICCID/VirtualNo,设备支持 VirtualNo/IMEI/SN,店铺使用店铺编号,订单使用订单号,退款使用退款单号。
|
||||
|
||||
### request 与 correlation 时间线
|
||||
|
||||
| 接口 | 参数 | 说明 |
|
||||
|---|---|---|
|
||||
| `/audit/requests/{request_id}/timeline` | path `request_id` | 来自 Event/Integration 返回字段、响应头或 Access Log;普通轮询为空时不调用 |
|
||||
| `/audit/correlations/{correlation_id}/timeline` | path `correlation_id` | 来自 Event、Integration、Outbox/任务或业务详情稳定字段 |
|
||||
|
||||
这两个接口不接受分页,返回在线窗口内全部已持久化关联节点。request 查询会同时返回 `access_log_lookup_request_id`,供运维复制到 Access Log 检索;接口自身不扫描 Access Log。
|
||||
|
||||
### 资金时间线 `GET /audit/finance/timeline`
|
||||
|
||||
| 字段 | 类型 | 前端来源 |
|
||||
|---|---|---|
|
||||
| `shop_id` | uint | 店铺详情或资金概况 |
|
||||
| `wallet_id` | uint | 代理主钱包或资产钱包详情 |
|
||||
| `order_id/order_no` | uint/string | 订单列表或详情 |
|
||||
| `payment_id/payment_no` | uint/string | 支付记录或已明确返回的支付单号 |
|
||||
| `refund_id/refund_no` | uint/string | 退款列表或详情 |
|
||||
| `recharge_id/recharge_no` | uint/string | 代理充值或个人资产充值 |
|
||||
| `approval_instance_id` | uint | 审批实例 |
|
||||
| `third_party_trade_no` | string | 已明确返回的第三方交易号 |
|
||||
| `actor_kind/actor_id` | enum/string | 调查某操作者涉及的资金事实,必须成对提供 |
|
||||
| `correlation_id` | string | 已有稳定业务链路时使用 |
|
||||
| `created_from/to/page/page_size` | 通用字段 | 时间和分页 |
|
||||
|
||||
调用方至少提供一个稳定资金条件;同一业务的其他 ID 由服务端解析,不要求前端补齐。
|
||||
|
||||
### 风险接口
|
||||
|
||||
| 接口 | 参数 | 映射 |
|
||||
|---|---|---|
|
||||
| `/audit/risks/overview` | `created_from/created_to/risk/result/action/source` | 独立风险中心筛选 |
|
||||
| `/audit/risks/events` | 上述字段 + `page/page_size` | `risks[].code→risk`、`results[].code→result`、`actions[].code→action`、`sources[].code→source` |
|
||||
|
||||
### Integration overview/list
|
||||
|
||||
overview 和 list 共用以下筛选;overview 另有 `bucket=hour/day`,list 另有 `page/page_size`。
|
||||
|
||||
| 字段 | 类型/枚举 | 来源与用途 |
|
||||
|---|---|---|
|
||||
| `integration_id` | string | 列表、通知 target 或 investigation_refs 返回的稳定 ID |
|
||||
| `provider` | `ctcc/cmcc/cucc/wechat_pay/alipay/fuiou/wecom/gateway` | 外部服务提供方 |
|
||||
| `direction` | `inbound/outbound` | 入站或出站 |
|
||||
| `operation` | OpenAPI enum | 外部操作稳定编码,使用列表/详情返回的 operation |
|
||||
| `result` | `pending/success/failed/unknown/not_found/invalid_payload/conflict/ignored/merged/rate_limited/completed/cancelled` | 原始结果 |
|
||||
| `result_category` | `processing/succeeded/indeterminate/failed/not_sent` | 服务端从 result 派生的固定类别 |
|
||||
| `external_id` | string | 外部系统业务或请求标识 |
|
||||
| `resource_type/resource_id/resource_key` | string | 本地主要资源稳定引用 |
|
||||
| `trigger_source/trigger_scene/trigger_series` | string | 触发来源、业务场景和显式尝试序列 |
|
||||
| `state_changed` | bool | 是否改变本地业务状态 |
|
||||
| `http_status` | 100~599 | 外部 HTTP 状态码 |
|
||||
| `provider_code` | string | 外部服务稳定结果码 |
|
||||
| `request_id/correlation_id` | string | 已有稳定链路字段时筛选;自动轮询 request_id 为空 |
|
||||
| `created_from/to` | 通用字段 | 时间范围,最长 31 天 |
|
||||
|
||||
当前 operation 枚举为:`realname_callback`、`realname_removal_callback`、`payment_precreate`、`payment_query`、`payment_callback`、`get_access_token`、`list_visible_members`、`list_visible_departments`、`get_template_detail`、`upload_approval_attachment`、`submit_approval`、`approval_callback`、`get_approval_detail`、`get_approval_info`、`query_realname_status`、`query_flow`、`query_card_status`、`query_device_info`、`set_speed_tier`、`stop_card`、`start_card`、`set_device_wifi`、`set_device_switch_mode`、`switch_device_card`、`reboot_device`、`reset_device`。前端使用 OpenAPI 或接口返回值,不维护另一份中文到编码映射。
|
||||
|
||||
### 代理与企业主体活动
|
||||
|
||||
| 接口 | `resource_type` | `identifier` | 其他参数 |
|
||||
|---|---|---|---|
|
||||
| `/agent/resource-activities/{resource_type}/{identifier}` | `iot_card/device/asset_allocation_record/exchange_order/shop/enterprise` | 依次使用 ICCID、VirtualNo、分配单号、换货单号、店铺编号、企业编号 | `created_from/created_to/page/page_size` |
|
||||
| `/enterprise/resource-activities/{resource_type}/{identifier}` | 仅 `iot_card/device` | 卡用 ICCID,设备用 VirtualNo | `created_from/created_to/page/page_size` |
|
||||
|
||||
代理店铺范围和企业 ID 只来自认证上下文,前端不能传 shop_id/enterprise_id 证明权限。
|
||||
|
||||
## 六、返回参数字典
|
||||
|
||||
### 统一响应与留存信息
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `code` | 统一响应码,成功为 0 |
|
||||
| `msg` | 中文响应消息 |
|
||||
| `data` | 本接口业务数据 |
|
||||
| `timestamp` | 服务端响应时间 |
|
||||
| `retention.online_from` | PostgreSQL 当前可在线查询的最早时间 |
|
||||
| `retention.archived_before` | 早于该时间的数据已归档,不可通过这些接口读取 |
|
||||
| `retention.timezone` | 留存边界时区,当前为 `Asia/Shanghai` |
|
||||
|
||||
### EventPage、EventDetail 与 EventView
|
||||
|
||||
`EventPage` 返回 `total/page/page_size/items[]/retention`;`EventDetail` 返回单个 EventView 并附带 retention。
|
||||
|
||||
| EventView 字段 | 含义/前端用法 |
|
||||
|---|---|
|
||||
| `event_id` | 稳定审计事件 ID,可打开事件详情 |
|
||||
| `occurred_at/created_at` | 业务事实发生时间/审计记录写入时间 |
|
||||
| `category` | 动作类别稳定编码 |
|
||||
| `action_code/action_name` | 稳定动作编码/中文展示名;筛选使用 code |
|
||||
| `summary` | 中文事件摘要 |
|
||||
| `actor_kind/actor_id/actor_name` | 操作者类型、稳定 ID、事件发生时名称快照 |
|
||||
| `actor_shop_id/name` | 操作者所属店铺快照 |
|
||||
| `actor_enterprise_id/name` | 操作者所属企业快照 |
|
||||
| `source` | admin_api、worker、scheduler、callback 等入口来源 |
|
||||
| `request_path/request_method/ip_address/user_agent` | HTTP 来源信息;Worker/Scheduler 可为空 |
|
||||
| `scope_type/scope_id/scope_name` | 业务范围类型、ID 和名称快照 |
|
||||
| `result/risk_level` | 结果和风险等级稳定编码 |
|
||||
| `error_code/error_summary` | 失败或拒绝时的稳定错误码和脱敏摘要 |
|
||||
| `request_id` | 有值才显示“请求链路”;自动轮询通常为空 |
|
||||
| `correlation_id` | 有值显示“业务链路” |
|
||||
| `parent_event_id` | 直接父事件 ID |
|
||||
| `batch_total/success_count/fail_count` | 批量根事件计数;非批量为 0 |
|
||||
| `metadata` | 已脱敏动作扩展字段,具体含义由 action_code 定义 |
|
||||
| `content_hash` | 事件不可变内容摘要 |
|
||||
| `resources[]` | 事件涉及的全部资源及各自快照 |
|
||||
| `investigation_refs` | 前端跨视角跳转的唯一稳定来源 |
|
||||
|
||||
`resources[]` 字段:`resource_type/resource_id/resource_key/display_name` 定位资源;`relation` 为 `primary/affected/reference`;`role` 为资源业务角色;`identity_snapshot` 为事件发生时身份;`before_data/after_data` 为该资源变更前后数据;`subject_visibility/subject_summary/subject_data` 为代理或企业安全投影;`sort_order/created_at` 为展示顺序和资源关联记录写入时间。
|
||||
|
||||
`investigation_refs` 字段:`event_id`、`actor_ref{kind,id}`、`resource_refs[]{resource_type,resource_id,resource_key,display_name}`、`request_id`、`correlation_id`、`integration_refs[]{integration_id}`。任一目标所需字段不完整时隐藏该入口。
|
||||
|
||||
### ResourceSearchPage
|
||||
|
||||
返回 `total/page/page_size/items[]/retention`。候选项包含 `resource_type/resource_id/resource_key/display_name/identity_snapshot/historical`;`historical=true` 表示当前业务表未命中,只从在线历史事件快照解析,不代表资源当前仍存在。
|
||||
|
||||
### LinkTimeline
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `request_id/correlation_id` | 本次查询使用的链路值,二者只会对应当前查询视角 |
|
||||
| `access_log_lookup_request_id` | 可复制到 Access Log 的 request ID |
|
||||
| `nodes[]` | 按发生时间稳定升序的跨事实节点 |
|
||||
| `retention` | Audit 与 Integration 的共同在线边界 |
|
||||
|
||||
`nodes[]` 包含:`record_source`、`node_id`、`occurred_at`、`code/title`、`result/result_name`、`summary`、`reference_only`、`request_id/correlation_id/parent_event_id`、`resources[]`、`investigation_refs`、`fidelity`。
|
||||
|
||||
`record_source` 可能是 `audit_event`、`integration_log`、`outbox_event`;由事件资源派生的只读引用还可能是 `asynq_task` 或 `domain_ledger_ref`。`reference_only=true` 只表示引用,不代表该节点独立改变了业务状态。`fidelity` 包含 `request_available`、`correlation_available`、`parent_event_available`、`direct_audit_link_available`、`stable_resource_available`;false 时禁止猜测补齐。
|
||||
|
||||
### FinanceTimelinePage
|
||||
|
||||
返回 `total/page/page_size/items[]/retention`。每个资金节点包含:
|
||||
|
||||
- `record_source/node_id/occurred_at`:事实来源、来源内稳定 ID、发生时间。
|
||||
- `code/title/result/result_name`:来源内稳定编码和中文名称。
|
||||
- `amount/balance_before/balance_after/currency`:金额和余额,单位分,人民币为 CNY;为空表示该节点不承载该金额。
|
||||
- `shop_id/wallet{resource_type,wallet_id}`:店铺和钱包引用。
|
||||
- `amount_authority{authoritative,table,field,conflict_rule}`:是否为权威金额及其数据库表字段;冲突时按 conflict_rule 展示。
|
||||
- `facts`:该事实来源的安全结构化业务字段。
|
||||
- `investigation_refs`:继续进入事件、资源、操作者、request、correlation 或 Integration。
|
||||
|
||||
### RiskOverview 与 RiskEventPage
|
||||
|
||||
RiskOverview 返回:`total`、服务端选择的 `bucket=hour/day`、`signals/risks/results/actions/sources` 分布、`trend[]` 和 retention。所有分布项统一为 `{code,name,count}`,筛选使用 code、展示使用 name。
|
||||
|
||||
- `signals[].code`:`high_risk/finance/security/failed/denied/partial/unknown`。
|
||||
- `risks[].code`:`low/normal/high/critical`。
|
||||
- `results[].code`:`success/failed/denied/partial/unknown`。
|
||||
- `actions[].code`:稳定 action_code。
|
||||
- `sources[].code`:`admin_api/personal_api/openapi/worker/scheduler/callback`。
|
||||
- `trend[]`:`bucket_at/total/high_risk/finance/security/failed/denied/partial/unknown`。
|
||||
|
||||
RiskEventPage 与 EventPage 结构相同,items[] 只包含固定风险集合内的 EventView。
|
||||
|
||||
### IntegrationOverview、ListPage 和 Detail
|
||||
|
||||
IntegrationOverview 返回 `total/anomaly_count/unknown_count/stale_pending_count/state_changed_count/average_duration_ms/p95_duration_ms/results/providers/directions/trend/retention`。`results[]` 为 `{code,name,category,count}`,providers/directions 为 `{code,name,count}`,`trend[]` 为 `{bucket_at,total,succeeded,processing,indeterminate,failed,not_sent}`。分布 code 可原样回填 list 筛选;name 只用于展示。
|
||||
|
||||
Integration ListPage 返回 `total/page/page_size/items[]/retention`。每项包含:
|
||||
|
||||
- `integration_id`:详情入口。
|
||||
- `provider/provider_name`、`direction/direction_name`、`operation/operation_name`:稳定编码和中文名。
|
||||
- `resource{type,id,key}`:type/id 均存在时可进入资源时间线。
|
||||
- `result/result_name/result_category`:原始结果、中文名和派生类别。
|
||||
- `duration_ms/state_changed/created_at`:耗时、本地状态是否变化和创建时间。
|
||||
- `request_id/correlation_id`:分别控制请求链路和业务链路入口;自动轮询 request_id 可为空。
|
||||
|
||||
Integration Detail 按分组返回:
|
||||
|
||||
| 分组 | 字段与用途 |
|
||||
|---|---|
|
||||
| `identity` | `integration_id/provider/provider_name/direction/direction_name/operation/operation_name/external_id` |
|
||||
| `resource` | `type/id/key`;type/id 齐全时进入资源时间线 |
|
||||
| `trigger` | `source/scene/series/attempt`;series 为空时不拼接其他重试 |
|
||||
| `result` | `code/name/category/http_status/provider_code/provider_message/duration_ms/state_changed/recovery_strategy`;本接口只展示,不执行恢复 |
|
||||
| `content` | 已脱敏 `request_summary/response_summary/metadata/content_hash` |
|
||||
| `linkage` | `request_id/correlation_id/audit_event_id`;稳定事件跳转优先使用 investigation_refs 中的 event_id |
|
||||
| `timestamps` | `scheduled_at/started_at/created_at/updated_at` |
|
||||
| `attempts[]` | 同 series 的 `integration_id/attempt/operation/operation_name/sent/result/result_name/result_category/duration_ms/state_changed/created_at` |
|
||||
| `fidelity` | `trigger_series_available/attempt_sequence_reliable/correlation_available/resource_id_available/provider_message_fidelity` |
|
||||
| `retention` | 在线留存边界 |
|
||||
|
||||
### SubjectActivityPage
|
||||
|
||||
代理与企业安全活动返回 `resource/total/page/page_size/items[]/retention`。
|
||||
|
||||
- `resource` 与 `related_resources[]`:`resource_type/resource_id/resource_key/display_name` 安全摘要;主体前端不得据此调用平台 `/audit/*`。
|
||||
- `items[].action_code/action_name`:安全动作编码和中文名。
|
||||
- `items[].subject_summary/subject_data`:写入时生成的主体安全摘要和白名单业务字段,不是平台 before/after 删除字段后的结果。
|
||||
- `items[].result/occurred_at`:结果和发生时间。
|
||||
- 响应不包含平台 actor、risk、内部原因、before/after、Audit Event ID、request_id、correlation_id、Integration 内容或 investigation_refs。
|
||||
|
||||
## 七、跨视角只读接口
|
||||
|
||||
| 视角 | 接口 | 入参来源 | 响应重点 |
|
||||
|---|---|---|---|
|
||||
@@ -14,7 +332,7 @@
|
||||
|
||||
以上接口仅允许超级管理员和平台账号访问,全部为 GET。认证身份只来自认证上下文;不提供导出、修改、删除、风险处置、自动封禁、重试、补偿或恢复能力。
|
||||
|
||||
## 资产和组织页面逐行导航
|
||||
### 资产和组织页面逐行导航
|
||||
|
||||
| 源页面 | 前置接口 | `response.data` 稳定字段 | 入口名称与可见条件 | 目标接口与参数映射 | 降级行为 |
|
||||
|---|---|---|---|---|---|
|
||||
@@ -29,7 +347,7 @@
|
||||
| 企业卡列表 | `GET /api/admin/enterprises/{id}/cards` | `items[].id/iccid/virtual_no/device_id` | 平台要求 `id`;企业要求 `iccid` 且当前授权有效;代理不从此列表进入 | 平台:`iot_card/{id}`;企业:`/enterprise/resource-activities/iot_card/{iccid}` | 路由中的企业 ID 不作为授权证明;后端始终使用认证上下文复核 |
|
||||
| 企业设备列表 | `GET /api/admin/enterprises/{id}/devices` | `items[].device_id/virtual_no` | 平台要求 `device_id`;企业要求 `virtual_no` 且当前授权有效;代理不从此列表进入 | 平台:`device/{device_id}`;企业:`/enterprise/resource-activities/device/{virtual_no}` | 字段为空或授权撤销时隐藏/显示活动不可用,不回退平台接口 |
|
||||
|
||||
## 账号、交易和资金页面逐行导航
|
||||
### 账号、交易和资金页面逐行导航
|
||||
|
||||
| 源页面 | 前置接口 | `response.data` 稳定字段 | 入口名称与可见条件 | 目标接口与参数映射 | 降级行为 |
|
||||
|---|---|---|---|---|---|
|
||||
@@ -45,7 +363,7 @@
|
||||
| 店铺主钱包流水 | `GET /api/admin/shops/{shop_id}/main-wallet/transactions` | path `shop_id`;`items[].id/asset_type/asset_id/asset_identifier` | 始终可按合法 path 显示资金入口;资产类型和 ID 齐全时显示资产审计 | 资金:`finance/timeline?shop_id={shop_id}`;审计:`resources/{asset_type}/{asset_id}/timeline` | 缺资产 ID 仍保留店铺资金入口,不按资产编号猜测 |
|
||||
| 资产钱包流水 | `GET /api/admin/assets/{identifier}/wallet/transactions` | 上层钱包接口 `wallet_id`;`items[].id/reference_type/reference_no` | 上层 `wallet_id` 非零时显示“资金链路” | `finance/timeline?wallet_id={wallet_id}` | `reference_type/reference_no` 仅展示;需后端节点明确返回资源引用后才能继续跳转 |
|
||||
|
||||
## 调查节点逐行跳转
|
||||
### 调查节点逐行跳转
|
||||
|
||||
| `investigation_refs` 字段 | 入口名称 | 可见条件 | 目标接口与参数 | 降级行为 |
|
||||
|---|---|---|---|---|
|
||||
@@ -56,9 +374,45 @@
|
||||
| `correlation_id` | “查看业务链路” | 非空 | `GET /api/admin/audit/correlations/{correlation_id}/timeline` | 空值隐藏,不按相近时间拼链路 |
|
||||
| `integration_refs[].integration_id` | “查看外部交互” | 非空 | `GET /api/admin/audit/integrations/{integration_id}` | 空值隐藏,不使用数据库主键或相似资源猜测 |
|
||||
|
||||
`actor_ref.kind` 第一阶段只使用 `account/openapi/system_task/scheduled_job/external_system`。代理和企业活动响应不得包含 `investigation_refs`。
|
||||
`actor_ref.kind` 使用 `account/personal_customer/openapi/system_task/scheduled_job/external_system`。代理和企业活动响应不得包含 `investigation_refs`。
|
||||
|
||||
## 六条完整调用链
|
||||
## 八、完整调用链
|
||||
|
||||
### HTTP 同步业务操作
|
||||
|
||||
1. 前端调用现有业务写接口;可不传 `X-Request-ID`,服务端会生成并通过响应头返回。
|
||||
2. HTTP 中间件建立 `request_id`,审计上下文初始设置 `correlation_id=request_id`。
|
||||
3. 业务变更与 Audit Event 在同一事务提交,事件写入 `request_id/correlation_id` 和资源快照。
|
||||
4. 前端从业务资源页用资源 ID 查看时间线,或从 EventView 的 `request_id` 查看该请求链路。
|
||||
5. 请求时间线只返回已经落入 Audit、Integration、Outbox 的节点;Access Log 需另用 `access_log_lookup_request_id` 检索。
|
||||
|
||||
### HTTP → Outbox → Asynq/Worker
|
||||
|
||||
1. HTTP 操作产生 `request_id`,业务确定或沿用 `correlation_id`。
|
||||
2. 创建 Outbox 时,未显式传入的 request/correlation/parent 从审计上下文继承并写入 `tb_outbox_event`。
|
||||
3. Relay 将三个字段随 envelope 交给消费者;需要继续入 Asynq 的任务在 payload 中显式携带。
|
||||
4. Worker 恢复系统操作者并写子 Audit Event;有外部调用时另写 Integration Log。
|
||||
5. 前端用 correlation 时间线查看跨进程链路;`record_source` 区分 Audit、Outbox、Integration 和任务引用,不能把 Outbox delivered 当作业务成功。
|
||||
|
||||
### 外部 Callback
|
||||
|
||||
1. Callback 同样经过 HTTP RequestID 中间件,因此会有 `request_id`。
|
||||
2. Integration Log 先记录入站外部交互;correlation 可等于 request ID,也可使用 payment_no、审批 SPNo 等更稳定业务键。
|
||||
3. Callback 实际改变本地业务事实时,以 `external_system` actor 写 Audit Event;没有状态变化时只保留 Integration Log。
|
||||
4. 前端从 Integration detail 的 request/correlation/resource 引用继续调查。
|
||||
|
||||
### 自动轮询
|
||||
|
||||
1. Scheduler/Asynq 选择卡并执行 Worker,没有 HTTP request,因此不生成 `request_id`。
|
||||
2. Worker 创建 Gateway Integration Log:`integration_id=trigger_series=correlation_id`,`request_id=null`。
|
||||
3. Gateway 结果若改变内部事实,Audit Event 使用同一 correlation,actor 为 `system_task`,source 为 `worker`。
|
||||
4. 前端从 Integration 调查中心、资源时间线或 correlation 时间线进入;隐藏“请求链路”。
|
||||
|
||||
### 人工即时刷新与手动轮询入队
|
||||
|
||||
- “人工即时刷新卡数据”在一个 HTTP 用例内直接执行 Gateway 查询:有 request_id,外部尝试可继承该 request/correlation,能从请求时间线串起。
|
||||
- “手动触发轮询”先记录 HTTP 触发 Audit Event,因此触发命令有 request_id;但当前 Redis 手动队列只保存 card ID,后续 Worker 没有原 request/correlation,执行阶段按新的 Integration ID 建链。
|
||||
- 因此当前不能仅凭 request_id 从“手动触发事件”自动跳到后续 Gateway 轮询尝试。前端分别展示手动任务进度、触发审计和资源/Integration 活动,不按时间拼接。若产品要求强关联,需要单独实施稳定 trigger/correlation 传播,不属于本文前端接入范围。
|
||||
|
||||
### 资产详情
|
||||
|
||||
@@ -102,7 +456,7 @@
|
||||
3. 从明细 `items[].investigation_refs` 直接进入事件、操作者、资源、request、correlation 或 Integration 视角。
|
||||
4. 缺少的引用入口隐藏;风险中心不提供处置、封禁或恢复按钮。
|
||||
|
||||
## 统一降级与事实边界
|
||||
## 九、统一降级与事实边界
|
||||
|
||||
- 缺少目标接口必需的稳定 ID 或 identifier 时隐藏入口,不按名称、中文描述、时间或编号前缀猜测。
|
||||
- 平台只有 Registry Key 时先调用精确资源搜索;零命中或多命中停留在搜索结果。
|
||||
|
||||
Reference in New Issue
Block a user