## ADDED Requirements ### Requirement: 平台审计调查接口必须使用平台身份边界 系统 MUST 仅允许超级管理员和平台账号访问 `/api/admin/audit` 下的内部审计调查接口。第一阶段 MUST NOT 要求细粒度审计权限码,也 MUST NOT 对平台账号增加店铺或企业数据范围过滤;代理、企业和个人客户 MUST 被后端拒绝,不能仅依赖前端隐藏页面。 #### Scenario: 平台账号查看全局审计 - **WHEN** 已认证平台账号请求内部审计接口 - **THEN** 系统返回全部数据范围内符合筛选条件的审计数据 - **AND** 不要求尚未稳定的细粒度审计权限码 #### Scenario: 非平台身份直接调用内部接口 - **WHEN** 代理、企业或个人客户绕过前端直接请求 `/api/admin/audit/events` - **THEN** 系统返回统一的禁止访问错误 - **AND** 不泄露是否存在匹配的审计事件 ### Requirement: 系统必须提供全局事件列表和事件详情 系统 SHALL 提供 `GET /api/admin/audit/events` 和 `GET /api/admin/audit/events/{event_id}`。列表 MUST 支持 `created_from`、`created_to`、`action`、`category`、`actor_kind`、`actor_id`、`source`、`result`、`risk`、`scope_type`、`scope_id`、`resource_type`、`resource_id`、`resource_key`、`request_id`、`correlation_id`、`page`、`page_size` 组合筛选;详情 MUST 返回事件上下文、操作者快照、结果、链路以及全部资源关系、身份快照和各资源前后变化。 #### Scenario: 组合筛选全局事件 - **WHEN** 平台账号按操作者、失败结果、资金类别和时间范围查询事件 - **THEN** 系统只返回同时满足条件的事件 - **AND** 每项包含稳定编码、中文名称、主要资源、结果和发生时间 #### Scenario: 查看多资源事件详情 - **WHEN** 平台账号查看一次换货审计事件 - **THEN** 系统返回换货单、旧卡/设备、新卡/设备、绑定、店铺及实际涉及的订单、钱包和套餐资源 - **AND** 每个资源分别返回 role、身份快照及自身 before/after ### Requirement: 系统必须提供操作者行为视角 系统 SHALL 提供 `GET /api/admin/audit/actors/{kind}/{id}/events`,按时间倒序展示指定人工账号、OpenAPI 账号、系统任务或外部系统的操作。查询 MUST 支持 action、result、risk、resource 和时间过滤,并使用事件中的操作者快照解释历史。 #### Scenario: 调查平台账号行为 - **WHEN** 调查人员查询某平台账号最近七天的操作 - **THEN** 系统返回该操作者的成功、失败、拒绝和部分成功事件 - **AND** 账号后来改名或删除不改变历史操作者名称快照 #### Scenario: 调查系统自动状态变化 - **WHEN** 调查人员查询 `system_task` 操作者类型 - **THEN** 系统返回 Worker 和 Scheduler 引发的内部状态变化 - **AND** 不将系统操作伪造成平台人工操作 ### Requirement: 系统必须提供通用资源搜索和资源时间线 系统 SHALL 提供 `GET /api/admin/audit/resources/search` 和 `GET /api/admin/audit/resources/{type}/{id}/timeline`。资源搜索 MUST 只使用 Resource Registry 已注册的精确标识或有索引关键词;资源时间线 MUST 通过 Event Resource 通用生成,不要求为每个领域新建审计表或时间线接口。 #### Scenario: 通过卡标识打开时间线 - **WHEN** 平台账号使用 ICCID 或 VirtualNo 搜索 IoT 卡并选择结果 - **THEN** 系统返回资源候选及稳定资源 ID - **AND** 时间线展示该卡作为 primary、affected 或 reference 参与的事件 #### Scenario: 查询设备某张绑定卡的轨迹 - **WHEN** 设备入口对其第二卡槽的卡执行操作 - **THEN** 同一事件出现在设备、卡和必要绑定关系的时间线 - **AND** 时间线能区分 `entry_device`、`bound_card` 和卡槽角色 #### Scenario: 历史资源已经删除或标识改变 - **WHEN** 当前业务表无法再返回事件发生时的资源名称或标识 - **THEN** 系统使用 Event Resource 保存的身份快照解释历史 ### Requirement: 系统必须提供请求和业务关联时间线 系统 SHALL 提供 `GET /api/admin/audit/requests/{request_id}/timeline` 和 `GET /api/admin/audit/correlations/{correlation_id}/timeline`。时间线 MUST 组合可关联的 Audit Event、Integration Log、Outbox/任务摘要及 Domain Ledger 引用,并为每个节点返回明确 `record_source`;系统 MUST NOT 把 Access Log 文件正文或不同事实复制成 Audit Event。 #### Scenario: 调查一次 HTTP 请求 - **WHEN** 平台账号通过 request ID 查询链路 - **THEN** 系统按发生时间返回该请求产生的内部操作、外部交互和可靠事件摘要 - **AND** 返回用于开发人员检索 Access Log 的 request ID 而不扫描日志文件 #### Scenario: 调查跨请求退款链路 - **WHEN** 平台账号通过退款 correlation ID 查询业务链路 - **THEN** 系统展示申请、审批、外部回调、退款处理、钱包回充、佣金和套餐后处理节点 - **AND** 每个金额或状态结论标明其 Domain Ledger 来源 #### Scenario: correlation 不能证明技术重试关系 - **WHEN** 多条 Integration Log 只有相同 correlation ID 而没有稳定 trigger series - **THEN** 业务时间线可展示它们属于同一业务链路 - **AND** 系统不得将它们标记为同一次外呼的重试序列 ### Requirement: 系统必须提供资金调查视角 系统 SHALL 提供 `GET /api/admin/audit/finance/timeline`,支持 `shop_id`、`wallet_id`、`order_id`、`order_no`、`payment_id`、`payment_no`、`refund_id`、`refund_no`、`recharge_id`、`recharge_no`、`approval_instance_id`、`third_party_trade_no`、`actor_kind`、`actor_id`、`correlation_id`、`created_from`、`created_to`、`page`、`page_size` 组合筛选。该视角 MUST 组合 Audit Event 与钱包流水、订单、支付、退款、充值、佣金和审批等 Domain Ledger,并明确金额权威来自业务流水而非 Audit Event;调用方只提供任一可用稳定业务条件时,Query MUST 在服务端解析关联事实,不得要求前端补齐同一链路全部 ID。 #### Scenario: 解释钱包余额变化 - **WHEN** 平台账号按钱包或交易流水查询资金时间线 - **THEN** 系统返回触发人、业务动作、订单/退款/充值、审批、金额和余额前后值 - **AND** 金额结论以钱包流水及对应业务表为准 #### Scenario: 资金事实与审计内容不一致 - **WHEN** Audit Event 摘要与 Domain Ledger 的金额事实出现差异 - **THEN** 接口明确标注数据来源并以 Domain Ledger 作为资金权威 - **AND** 不通过修改历史 Audit Event 掩盖差异 ### Requirement: 系统必须提供风险和异常视角 系统 SHALL 提供 `GET /api/admin/audit/risks/overview` 和 `GET /api/admin/audit/risks/events`,展示高风险、资金、安全、失败、拒绝、部分成功和结果未知事件的数量、趋势与明细。总览 MUST 限定时间范围,明细 MUST 可跳转到事件、资源、操作者和 correlation 视角。 #### Scenario: 查看失败和拒绝趋势 - **WHEN** 平台账号查询最近二十四小时风险总览 - **THEN** 系统按风险、结果、action 和来源返回聚合数量与时间趋势 - **AND** 不把普通低风险成功事件计入异常数量 #### Scenario: 从风险事件继续调查 - **WHEN** 平台账号选择一条高风险资金拒绝事件 - **THEN** 系统返回稳定事件 ID及其操作者、资源和业务链路跳转信息 ### Requirement: 查询响应必须稳定、分页且面向前端投影 所有审计查询 SHALL 使用专用 DTO 和统一响应 `{code,msg,data,timestamp}`,不得直接返回 GORM Model。列表 MUST 默认每页 20、最大 100,使用时间与稳定 ID 排序;Query MUST 批量加载资源与业务引用,避免 N+1,并满足 API P95 < 200ms、P99 < 500ms、数据库查询 < 50ms 的目标。 #### Scenario: 稳定翻页 - **WHEN** 多条事件具有相同发生时间且调用方连续翻页 - **THEN** 系统使用发生时间和稳定 ID 作为排序游标或等价稳定排序 - **AND** 不重复或遗漏事件 #### Scenario: 查询多资源列表 - **WHEN** 一页包含多个动作和资源类型 - **THEN** Query 批量加载 Event Resource 与必要展示信息 - **AND** 不为每条事件逐一查询操作者、资源或 Domain Ledger ### Requirement: 平台调查接口必须明确查询入参来源 平台调查接口的筛选字段 SHALL 来自调查人员输入或上一个视角携带的稳定跳转值;认证用户类型和当前账号 ID MUST 来自认证上下文,不得由 query、path 或 body 指定。事件 ID、资源 ID、actor ID、request ID、correlation ID 和资金业务标识 MUST 使用列表、详情、业务页面或已记录节点提供的稳定值,Query MAY 根据稳定 ID 批量派生中文名称、资源展示信息、关联节点和 Domain Ledger 内容,但 MUST NOT 按时间接近、中文描述或模糊关键词猜测关联。 #### Scenario: 从事件节点跳转到其他视角 - **WHEN** 调查人员从事件、资源、操作者、风险或链路节点继续调查 - **THEN** 前端使用该节点返回的稳定 event、resource、actor、request 或 correlation 标识构造目标接口入参 - **AND** 后端只校验和查询该稳定标识,不猜测缺失关联 #### Scenario: 调查人员直接筛选 - **WHEN** 调查人员在全局、资源搜索、资金或风险视角填写时间、动作、结果、风险或业务标识 - **THEN** Handler 从受控 query/path 参数读取筛选条件并执行格式、枚举、分页及时间范围校验 - **AND** 未提供的筛选条件不由后端补猜 #### Scenario: 身份范围来自认证上下文 - **WHEN** 任一调用方请求平台调查接口 - **THEN** Handler 仅从认证上下文取得用户类型和当前账号 ID - **AND** 忽略或拒绝调用方伪造的店铺、企业或身份范围参数 ### Requirement: 业务页面必须具备可执行的审计导航契约 系统 SHALL 为已纳入第一阶段的业务列表和详情冻结“前置接口、`response.data` 字段、入口可见条件、目标审计接口、参数映射和降级行为”。账号、店铺、企业、卡、设备、设备卡槽、资产详情、分配、换货、订单、退款、代理充值、资产钱包及店铺资金页面 MUST 使用其现有响应中的稳定 ID 或 Registry Key;前端 MUST NOT 解析中文名称、备注或编号前缀推断资源。 #### Scenario: 平台从卡资产详情查看审计 - **WHEN** `GET /api/admin/assets/resolve/{identifier}` 返回 `asset_type=card`、`asset_id` 和 `iccid` - **THEN** 平台页面使用 `resource_type=iot_card` 与 `resource_id=asset_id` 调用 `GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline` - **AND** 不使用 ICCID 再搜索一次或把 `card` 直接当成未注册资源类型 #### Scenario: 平台从订单详情查看资金链路 - **WHEN** `GET /api/admin/orders/{id}` 返回订单 `id`,但没有返回全部 Payment、Refund 或 Integration 标识 - **THEN** 页面调用 `GET /api/admin/audit/finance/timeline?order_id={id}` - **AND** Query 在服务端解析关联事实,不要求前端补猜缺失 ID #### Scenario: 前置响应缺少稳定引用 - **WHEN** 业务响应没有目标审计接口需要的稳定 ID 或 Registry Key - **THEN** 前端不展示对应入口,后端也不按名称、时间、描述或编号前缀推断 ### Requirement: 平台调查节点必须返回统一跳转引用 平台事件、资源、操作者、request、correlation、资金、风险及 Integration 关联节点 SHALL 返回统一的 `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}`。前端 MUST 仅使用存在的稳定引用导航;代理和企业响应 MUST NOT 复用或暴露该内部结构。 #### Scenario: 从风险节点继续调查 - **WHEN** 风险事件节点同时包含 event、actor、resource 和 correlation 引用 - **THEN** 前端可分别调用事件详情、操作者事件、资源时间线和 correlation 时间线 - **AND** 每个目标参数直接来自 `investigation_refs` 对应字段 #### Scenario: 节点缺少关联标识 - **WHEN** 节点没有 request、correlation 或 Integration 稳定标识 - **THEN** 前端隐藏对应跳转,不按相近时间或相同资源猜测链路 #### Scenario: 旧日志不能跳新审计 - **WHEN** 平台查看独立旧 operation log 入口中的切换前记录 - **THEN** 页面不为该记录构造新 Audit Event、资源时间线或 correlation 跳转 ### Requirement: 调查接口必须只读并完整展示已存业务字段 第一阶段所有平台调查接口 MUST 只读,MUST NOT 提供 Audit Event 修改、删除、导出、风险处置、重试、补偿、绑定或恢复操作。平台接口 SHALL 返回审计库中已保存的手机号、IP、ICCID、VirtualNo、金额、交易号和 before/after 等完整业务字段;系统安全凭据及原始第三方敏感正文 MUST 在写入前删除,因此任何查询均不得返回。 #### Scenario: 平台查看业务敏感字段 - **WHEN** 平台账号查看资金或资产事件详情 - **THEN** 系统返回已保存的完整业务金额、资产标识、操作者 IP 和业务前后值 - **AND** 不执行展示层掩码 #### Scenario: 请求审计导出或修改 - **WHEN** 调用方尝试通过审计中心导出、修改或删除审计记录 - **THEN** 系统不存在对应写接口 #### Scenario: 系统安全凭据不存在于详情 - **WHEN** 平台账号查看支付、企微或系统配置相关事件 - **THEN** 响应不包含密码、Token、Secret、私钥、回调凭据、Authorization、Cookie、签名 URL 或支付密钥 ### Requirement: 调查接口必须公开在线留存边界 全局、操作者、资源、request、correlation、资金和风险查询 MUST 在 DTO 中返回 `retention{online_from, archived_before, timezone}`。系统 MUST 只查询 PostgreSQL 在线窗口;显式时间范围早于或跨越 `online_from` 时 MUST 返回稳定的已归档错误及当前在线边界,不得返回误导性空结果或不完整时间线。第一阶段 MUST NOT 从对象存储补查历史。 #### Scenario: 业务页面打开资源审计 - **WHEN** 平台从卡、设备、订单或其他业务详情打开资源时间线且未指定历史时间 - **THEN** 接口返回当前在线窗口内的事件及 retention 元数据 - **AND** 页面明确提示早于 `online_from` 的记录已转冷归档、当前不可在线查询 #### Scenario: 请求上月已归档时间线 - **WHEN** 调查人员显式指定的时间范围全部早于 `online_from` - **THEN** 接口返回稳定的已归档错误和当前在线边界 - **AND** 不返回成功空列表、不扫描对象存储