准备提案

This commit is contained in:
2026-07-29 12:20:12 +08:00
parent b3a215b19e
commit eea19f2a5b
15 changed files with 2146 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
## MODIFIED Requirements
### Requirement: 记录所有账号管理操作
系统 SHALL 通过统一 Audit Event Writer 记录所有账号管理操作,包括创建、更新、删除、企业微信绑定、角色分配和角色移除;成功事件 MUST 与对应业务事实采用该用例约定的可靠事务策略,失败或拒绝事件 MUST 在业务回滚后使用独立短事务写入,不得继续向 `tb_account_operation_log` 新增记录。
#### Scenario: 创建账号时记录统一审计事件
- **WHEN** 用户创建账号成功
- **THEN** 系统写入 `account.created` Audit Event
- **AND** 事件包含操作人、目标账号资源、账号业务标识快照和创建后的关键业务字段
#### Scenario: 更新账号时记录资源级变更
- **WHEN** 用户更新账号信息(用户名、手机号、状态等)
- **THEN** 系统写入账号资源的 `before_data``after_data`
- **AND** 仅包含本次操作相关的业务字段及其变更
#### Scenario: 删除账号时保留目标快照
- **WHEN** 用户软删除账号成功
- **THEN** 系统记录删除事件及账号删除前的关键业务字段
- **AND** 账号后续无法从业务表查询时仍可通过资源标识快照识别目标账号
#### Scenario: 分配角色时记录账号与角色资源
- **WHEN** 用户为账号分配角色
- **THEN** 系统记录账号为主要资源、实际分配角色为关联资源的 Audit Event
- **AND** 事件包含实际生效的角色 ID 与角色名称快照
#### Scenario: 移除角色时记录账号与角色资源
- **WHEN** 用户移除账号的角色
- **THEN** 系统记录账号为主要资源、被移除角色为关联资源的 Audit Event
- **AND** 事件包含被移除角色 ID 与角色名称快照
### Requirement: 审计日志包含完整的操作上下文
系统 SHALL 在统一 Audit Event 中记录操作者快照、来源、结果、目标资源、资源级变更及请求关联上下文,并 MUST 支持一次操作关联多个资源。
#### Scenario: 记录操作人信息
- **WHEN** 记录账号管理 Audit Event
- **THEN** 事件包含稳定的操作者类型、操作者 ID、操作者名称快照和来源
#### Scenario: 记录目标账号信息
- **WHEN** 记录账号管理 Audit Event
- **THEN** 事件至少关联一个账号主要资源
- **AND** 资源快照包含账号 ID、用户名和账号类型
#### Scenario: 记录变更数据
- **WHEN** 账号管理操作改变业务字段
- **THEN** 账号资源的 `before_data``after_data` 使用 JSONB 保存本次操作相关字段
- **AND** 密码、验证码、Token、Secret、私钥、Authorization、Cookie 及其他系统安全凭据在写入前被删除
#### Scenario: 记录请求上下文
- **WHEN** 账号管理操作来自 HTTP 请求
- **THEN** 事件包含 `request_id`、IP、User-Agent、请求方法和请求路径
- **AND** 可通过 `request_id` 关联 Access Log
#### Scenario: 记录执行结果
- **WHEN** 已识别操作人或目标账号的操作成功、失败或被拒绝
- **THEN** 事件记录对应的 `success``failed``denied` 结果及稳定错误码
### Requirement: 操作描述使用中文
系统 SHALL 为账号审计动作维护稳定英文动作编码和中文显示名称,查询接口 MUST 返回动作编码与中文显示名称,不得把可变中文描述作为动作身份或资源关联依据。
#### Scenario: 创建操作描述
- **WHEN** 查询账号创建事件
- **THEN** 响应返回稳定动作编码 `account.created` 和中文显示名称“创建账号”
#### Scenario: 更新操作描述
- **WHEN** 查询账号更新事件
- **THEN** 响应返回稳定动作编码 `account.updated` 和中文显示名称“更新账号”
#### Scenario: 删除操作描述
- **WHEN** 查询账号删除事件
- **THEN** 响应返回稳定动作编码 `account.deleted` 和中文显示名称“删除账号”
#### Scenario: 分配角色操作描述
- **WHEN** 查询账号角色分配事件
- **THEN** 响应返回稳定动作编码和中文显示名称“分配账号角色”
### Requirement: 支持按多维度查询审计日志
系统 SHALL 通过统一审计 Query 支持按操作者、账号资源、动作、结果、时间、`request_id``correlation_id` 组合筛选,并 SHALL 使用适配分页倒序查询的索引满足数据库查询低于 50ms 的性能目标。
#### Scenario: 按操作人查询日志
- **WHEN** 平台用户查询特定操作者的账号相关事件
- **THEN** 系统按操作者及时间索引返回倒序分页结果
#### Scenario: 按目标账号查询日志
- **WHEN** 平台用户查询特定账号的资源时间线
- **THEN** 系统返回该账号作为主要、受影响或引用资源关联的事件
#### Scenario: 按时间范围查询日志
- **WHEN** 平台用户查询最近 7 天的账号相关事件
- **THEN** 系统使用时间及事件 ID 进行稳定倒序分页
### Requirement: 关联访问日志追溯完整请求链路
系统 SHALL 通过 `request_id` 关联 Audit Event 与 Access Log并 SHALL 通过 `correlation_id``parent_event_id` 关联同一业务操作的后续任务、回调和子事件。
#### Scenario: 通过request_id关联日志
- **WHEN** Audit Event 记录 `request_id="req-12345"`
- **THEN** 平台调查人员可以使用同一 `request_id` 定位对应的 HTTP Access Log
#### Scenario: 追溯完整请求链路
- **WHEN** 平台调查人员查询某个账号管理操作
- **THEN** 系统返回该请求关联的 Audit Event 及可用的后续关联事件
- **AND** Access Log 继续负责完整 HTTP 请求响应排障,不由 Audit Event 复制完整请求正文
## REMOVED Requirements
### Requirement: 异步写入不阻塞业务流程
**Reason**: 旧实现通过裸 Goroutine、部分调用方双重 Goroutine 写入账号操作日志,无法保证事务一致性、进程退出前落库、顺序和失败闭环,不满足资金与后台追责要求。
**Migration**: 所有新账号及借用账号日志的写入口迁移到统一 Audit Event Writer高风险成功操作与业务事实同一 GORM 事务,其他成功操作遵循用例明确的事务策略,失败和拒绝使用独立短事务。旧表原样保留,不回填、不转换且不进入新审计中心。
#### Scenario: 异步写入审计日志
- **WHEN** AccountService.Create 创建账号成功
- **THEN** 主流程立即返回,审计日志在独立 Goroutine 中异步写入
#### Scenario: 写入失败只记录错误日志
- **WHEN** 审计日志写入数据库失败
- **THEN** 记录 Error 级别日志,包含完整审计信息,但不影响业务操作结果
#### Scenario: 业务响应时间不受影响
- **WHEN** 执行账号创建操作
- **THEN** API 响应时间不应因审计日志写入而增加(< 1ms

View File

@@ -0,0 +1,114 @@
## MODIFIED Requirements
### Requirement: 资产操作审计日志必须补充业务可读字段
系统 SHALL 将卡、设备、绑定关系、换货单和相关店铺作为独立 Audit Event Resource 记录,并 MUST 为每个资源保存事件发生时的业务标识快照及该资源自己的 `before_data``after_data`,确保业务侧无需查询当前业务表即可理解历史操作。
#### Scenario: 卡相关事件保存完整关键标识
- **WHEN** 系统记录针对单卡或多卡的分配、回收、删除、实名、停复机、绑定或换货事件
- **THEN** 每张卡资源快照除内部卡 ID 外还包含当时可用的 ICCID、虚拟号及其他已登记关键业务标识
#### Scenario: 设备相关事件保存完整关键标识
- **WHEN** 系统记录设备绑定、解绑、切卡、远程控制、删除或换货事件
- **THEN** 每台设备资源快照除内部设备 ID 外还包含当时可用的虚拟号、IMEI、SN 及其他已登记关键业务标识
#### Scenario: 店铺相关事件保存名称快照
- **WHEN** 系统记录资产分配、资产回收、归属变更或其他涉及店铺的事件
- **THEN** 店铺资源快照在店铺 ID 之外还包含事件发生时的店铺名称
#### Scenario: 设备中的卡作为独立资源
- **WHEN** 一次操作通过设备入口影响设备中的某张卡
- **THEN** 事件分别关联设备资源与卡资源
- **AND** 使用资源角色表明入口设备、受影响卡或引用关系,不得只把卡嵌入设备 JSON
#### Scenario: 换货同时记录旧卡和新卡
- **WHEN** 换货操作将旧卡替换为新卡
- **THEN** 同一事件分别关联 `old_card``new_card` 资源角色
- **AND** 两张卡资源快照分别保存自身当时的虚拟号和 ICCID
### Requirement: 审计日志可读字段必须遵循兼容新增原则
系统 SHALL 在统一 Audit Event 切换后保留旧 `tb_asset_operation_log` 原始数据,但 MUST NOT 回填、转换或投影到新审计中心;新事件不得为兼容旧单资产结构而丢失多资源关系。
#### Scenario: 旧历史保持独立
- **WHEN** 平台需要查询切换前的资产操作历史
- **THEN** 系统仅通过既有旧资产日志入口读取原表内容
- **AND** `/api/admin/audit/*`、通用资源时间线和代理/企业资源活动接口不返回旧表记录
#### Scenario: 新增结构不修改旧历史
- **WHEN** 统一 Audit Event 上线
- **THEN** 系统不在线回填、重写或删除旧资产日志
- **AND** 新操作只写统一 Audit Event不再向旧资产日志表新增记录
#### Scenario: 不为旧记录补造新结构
- **WHEN** 旧资产日志仅保存单资产字段或不完整 JSON
- **THEN** 系统不为其创建 Audit Event、Event Resource、结果、风险或链路字段
- **AND** 不得从中文描述猜测多资源关系
### Requirement: 同类审计场景必须使用统一的可读字段命名
系统 SHALL 通过 Resource Registry 为同类资产资源定义稳定的类型、角色与标识快照字段,平台查询和安全资源活动投影 MUST 使用一致语义。
#### Scenario: 单卡与批量卡标识命名一致
- **WHEN** 系统分别记录单卡和批量卡事件
- **THEN** 每张卡均使用同一资源类型及 `iccid``virtual_no` 快照字段
- **AND** 批量场景通过多个资源关联表达,不另造语义不同的聚合字段
#### Scenario: 设备标识命名稳定
- **WHEN** 系统记录多个设备相关事件
- **THEN** 设备资源统一使用 `virtual_no``imei``sn` 等已登记字段名
#### Scenario: 资源角色表达换货与绑定语义
- **WHEN** 事件涉及入口设备、绑定卡、旧卡、新卡或目标店铺
- **THEN** 系统使用注册表中的稳定资源角色编码表达关系
- **AND** 查询接口返回对应中文角色名称
### Requirement: 审计日志补充可读字段不得引入额外破坏性变更
系统 SHALL 仅替换资产审计写入与查询投影,不得借本次变更调整资产、店铺、账号、企业授权或换货的既有业务规则。
#### Scenario: 店铺删除规则保持不变
- **WHEN** 本次变更上线
- **THEN** 店铺删除相关业务规则不因审计切换而发生变化
#### Scenario: 企业账号列表行为保持不变
- **WHEN** 本次变更上线
- **THEN** 账号列表接口的企业账号展示逻辑不因审计切换而发生变化
#### Scenario: 企业资产授权规则保持不变
- **WHEN** 企业查询资源安全活动
- **THEN** 系统复用现有有效卡和设备授权关系判断归属
- **AND** 不改变授权、撤销或资产归属业务事实
## ADDED Requirements
### Requirement: 旧资产操作日志接口必须收缩为平台历史入口
系统 SHALL 在安全资源活动接口可用后,将旧 `GET /api/admin/assets/:identifier/operation-logs` 收缩为仅超级管理员和平台用户可访问的过渡历史入口,并 MUST 停止向代理和企业返回内部资产审计 DTO。
#### Scenario: 平台查询旧资产日志
- **WHEN** 超级管理员或平台用户在过渡期调用旧资产操作日志接口
- **THEN** 系统允许读取对应资产的历史旧日志
- **AND** 不对旧表执行修改或删除
#### Scenario: 代理改用安全活动接口
- **WHEN** 代理调用旧资产操作日志接口
- **THEN** 系统返回无权限错误
- **AND** 代理通过独立安全资源活动接口查询自身范围内的业务结论
#### Scenario: 企业改用安全活动接口
- **WHEN** 企业调用旧资产操作日志接口
- **THEN** 系统返回无权限错误
- **AND** 企业通过独立安全资源活动接口查询当前企业有效授权资产的业务结论
### Requirement: 旧资产日志裸 Goroutine 写入必须停止
系统 MUST 使用统一 Audit Event Writer 替代资产审计服务的裸 Goroutine 写入;高风险成功操作 MUST 与业务事实同一 GORM 事务,失败或拒绝 MUST 在业务回滚后使用独立短事务写入。
#### Scenario: 新资产操作不再写旧表
- **WHEN** 某资产写用例完成统一 Audit Event 接入并切换上线
- **THEN** 该用例不再向 `tb_asset_operation_log` 新增记录
#### Scenario: 统一审计写入失败
- **WHEN** 高风险资产操作的 Audit Event 在业务事务内写入失败
- **THEN** 业务事务回滚并返回统一错误
- **AND** 不得通过后台 Goroutine 补写后假装与业务事实原子一致
#### Scenario: 失败事件独立落库
- **WHEN** 资产业务操作已回滚且需要记录 `failed``denied` 事件
- **THEN** 系统使用独立短事务写入失败或拒绝 Audit Event
- **AND** 二次写入失败时保留原业务错误并记录 critical 日志和指标

View File

@@ -0,0 +1,153 @@
## ADDED Requirements
### Requirement: 统一且不可变的内部审计事件
系统 SHALL 以统一 Audit Event 记录内部业务操作事实,并 SHALL 将 Audit Event 与 Access Log、Integration Log、Domain Ledger、Outbox 的职责保持分离。Audit Event MUST 至少保存稳定动作编码、动作中文名、操作者快照、入口来源、结果、风险等级、发生时间以及可用的 `request_id``correlation_id``parent_event_id`。已创建的 Audit Event 和资源关联在 PostgreSQL 保留期间 MUST 不可修改,业务接口和业务 Repository MUST 不可删除;后续业务修正 MUST 产生新的业务事件并关联原业务链路。唯一删除例外是内部 Retention Worker 在对象存储归档完整性门禁通过后按完整月份物理删除 PostgreSQL 上月 Audit Event 与 Event Resource禁止使用软删除或数据库历史表替代第一阶段 MUST NOT 提供专用审计纠错、删除或恢复写接口。
#### Scenario: 平台账号修改店铺状态
- **WHEN** 已识别的平台账号将某店铺从启用修改为禁用
- **THEN** 系统写入包含操作者账号 ID、名称、账号类型、动作编码、来源、成功结果、店铺资源及状态前后值的不可变 Audit Event
#### Scenario: 后续业务操作修正原业务结果
- **WHEN** 新的受控业务操作修正此前业务结果
- **THEN** 系统为新业务操作写入独立 Audit Event 并通过 correlation 或 parent 关联原链路
- **AND** 不更新或删除原 Audit Event
#### Scenario: 归档成功后删除数据库上月数据
- **WHEN** 上一完整自然月的 Audit Event、Event Resource 和关联归档 manifest 已通过数量、对象存在性与 SHA-256 校验
- **THEN** Retention Worker 可以按留存策略删除 PostgreSQL 中该月 Audit Event 与 Event Resource
- **AND** 对象存储中的该月备份继续长期保留
- **AND** 该清理不构成业务更正、业务删除接口或审计事实丢弃
### Requirement: 审计记录边界
系统 SHALL 记录所有会改变业务事实、权限、配置、安全状态或产生外部副作用的非查询操作,并 SHALL 记录已确定主要业务资源后的 `success``failed``denied``partial``unknown` 结果。系统任务和外部回调引起内部状态变化时 MUST 记录 Audit Event。普通查询、参数解析前失败、只有操作者但无法确定主要业务资源的非法请求 MUST NOT 生成 Audit Event只进入 Access Log 或对应安全日志Action Registry 明确标记的敏感读取除外。仅发生外部交互但未改变内部业务事实时 MUST 只写 Integration Log。
#### Scenario: 业务规则拒绝已识别资源的操作
- **WHEN** 操作者对已识别订单发起取消但订单状态不允许取消
- **THEN** 系统写入 `denied` Audit Event并关联该订单和操作者
#### Scenario: 参数解析前失败
- **WHEN** 请求因 JSON 格式错误而无法识别操作者意图对应的业务资源
- **THEN** 系统不写 Audit Event且由 Access Log 记录请求失败
#### Scenario: 只有操作者但无法定位业务资源
- **WHEN** 已认证操作者发起非法操作但系统无法确定任何主要业务资源 Key 或快照
- **THEN** 系统不写无资源 Audit Event并由 Access Log 或安全日志记录
#### Scenario: Registry 标记的敏感读取
- **WHEN** 平台账号读取被 Action Registry 标记为敏感的审计详情或明文业务凭证
- **THEN** 系统在返回结果前写入关联读取者、目标资源和字段类别的 Audit Event
- **AND** 审计写入失败时不返回敏感结果
#### Scenario: 外部回调改变内部状态
- **WHEN** 支付或实名回调已写 Integration Log 且随后改变订单、支付、充值或卡的内部状态
- **THEN** 系统写入关联该 Integration Log 和内部资源的 Audit Event
### Requirement: 操作者与入口来源快照
每个 Audit Event MUST 保存事件发生时的真实操作者类型、操作者 ID、可读名称、账号类型以及适用的店铺或企业上下文。操作者类型 SHALL 覆盖平台账号、代理账号、企业账号、个人客户、Open API 调用方、外部系统和系统任务。系统自动步骤 MUST 使用 `system``external_system`MUST NOT 伪造为最初发起人;最初发起人 SHALL 通过父事件或关联链路查询获得。
#### Scenario: Asynq 后续任务完成业务状态变化
- **WHEN** 一个由用户操作触发的 Asynq 任务稍后改变套餐或资金状态
- **THEN** 子事件的操作者类型为系统任务,并通过 `parent_event_id``correlation_id` 关联最初用户事件
#### Scenario: 企业微信审批终态回调
- **WHEN** 企业微信回调推动退款或线下充值进入终态
- **THEN** 回调事件记录外部系统为来源,并保留业务申请的真实提交人作为关联资源或链路事实,而非把本地账号伪造为外部审批人
### Requirement: 一等资源关联与资源级变化
每个 Audit Event MUST 至少关联一个 `primary` 资源,并 SHALL 支持任意数量的 `affected``reference` 资源。每个资源关联 MUST 独立保存资源类型、资源 ID、资源角色、事件发生时的稳定业务标识快照以及该资源自身的 `before_data``after_data`。删除、重新绑定、换号或修改当前业务表后,历史资源快照 MUST 保持不变。系统 MUST NOT 把多资源变化压缩为事件级单一前后 JSON。
#### Scenario: 店铺合同或状态变更可追责
- **WHEN** 操作者修改某店铺的合同相关配置或状态
- **THEN** 事件同时包含操作者快照和店铺 ID、店铺编号、店铺名称快照并将变更字段保存于该店铺资源的前后数据
#### Scenario: 已删除资源仍可识别
- **WHEN** 某卡、设备、账号或店铺在事件发生后被删除或更名
- **THEN** 历史事件仍使用事件发生时保存的业务标识快照展示该资源
### Requirement: 当前真实领域的资源注册契约
Action Registry 和 Resource Registry MUST 以当前代码盘点结果覆盖账号与权限、店铺与企业、个人客户、卡与设备、设备卡槽绑定、资产分配与授权、换货、套餐系列与套餐权益、订单与支付、退款与充值、代理和资产钱包、佣金提现、审批、系统及外部连接配置、导入批量、通知、轮询监控、Integration Log、Outbox 和异步任务。每种资源类型 MUST 定义稳定类型编码、中文名称、最小业务标识快照字段和禁止记录字段;未注册资源 MUST 不得静默降级为无语义 JSON。
#### Scenario: 注册 IoT 卡资源
- **WHEN** 动作关联 IoT 卡
- **THEN** 资源快照至少包含卡 ID、ICCID、VirtualNo并按业务需要包含 MSISDN、运营商、所属店铺、套餐系列和资产世代
#### Scenario: 注册设备资源
- **WHEN** 动作关联设备
- **THEN** 资源快照至少包含设备 ID、VirtualNo、IMEI、SN并按业务需要包含设备名称、型号、所属店铺、套餐系列和资产世代
#### Scenario: 注册资金资源
- **WHEN** 动作改变代理钱包或资产钱包
- **THEN** 事件关联钱包、店铺或卡/设备、业务单据和唯一交易流水,并保存金额、变更前余额、变更后余额、币种与业务引用
### Requirement: 设备与卡槽关系可独立追踪
设备、IoT 卡和设备卡槽绑定 SHALL 均作为一等资源。对“某设备的某张卡”执行的操作 MUST 同时关联入口设备、目标卡和绑定关系,并 MUST 保存卡槽位置及是否当前卡。设备当前卡切换 MUST 分别关联旧当前卡、新当前卡及对应绑定关系。
#### Scenario: 操作设备第二卡槽中的卡
- **WHEN** 操作者通过设备入口对第二卡槽中的 IoT 卡执行停复机、实名、限速或其他卡操作
- **THEN** 事件以该卡作为主要或受影响资源,同时保存设备标识、绑定关系、`slot_position=2` 和当时的 `is_current`
#### Scenario: 切换设备当前卡
- **WHEN** 设备从卡 A 切换到卡 B
- **THEN** 同一业务事件关联设备、卡 A、卡 B 及两个卡槽绑定,并分别保存其前后当前卡状态
### Requirement: 换货完整业务边界审计
换货事件 SHALL 关联换货单、旧资产、新资产、所属店铺以及本次操作实际影响的卡槽绑定、个人客户绑定、资产钱包、钱包流水、套餐权益和资产状态。卡换货快照 MUST 同时保存旧卡和新卡各自的 ICCID 与 VirtualNo设备换货 MUST 保存旧设备和新设备各自的 VirtualNo、IMEI、SN并 SHALL 对实际涉及的绑定卡逐张建立资源关联。
#### Scenario: 卡换货并迁移数据
- **WHEN** 卡换货完成且迁移钱包、套餐权益和个人客户绑定
- **THEN** 事件关联换货单、旧卡、新卡、旧新钱包、资金流水、被迁移套餐权益和客户绑定,并在旧新卡快照中分别保存 ICCID 与 VirtualNo
#### Scenario: 设备换货涉及多张绑定卡
- **WHEN** 设备换货影响旧设备或新设备的多张绑定卡
- **THEN** 事件除旧新设备外还逐张关联实际受影响的卡和卡槽绑定,使设备时间线与每张卡时间线均可定位该换货
### Requirement: 资金操作的同事务审计
钱包扣款、入账、退款、预占、释放、提现、佣金、信用额度和其他资金操作的成功 Audit Event MUST 与对应钱包、交易流水、订单、退款、充值或提现业务事实处于同一 GORM 事务。Audit Event 写入失败 MUST 使业务事务回滚。资金事件 MUST 关联唯一 Domain Ledger 事实Audit Event MUST NOT 替代钱包流水、订单或支付记录。
#### Scenario: 钱包扣款成功
- **WHEN** 订单使用代理主钱包完成扣款
- **THEN** 钱包余额、唯一交易流水、订单支付事实、Audit Event 以及需要的 Outbox 在同一事务提交,任一关键写入失败均回滚
#### Scenario: 退款回充成功
- **WHEN** 已批准退款向原支付钱包回充
- **THEN** 退款事件关联退款单、原订单、付款钱包、原扣款流水和退款流水,并保存退款金额及余额前后值
### Requirement: 失败与拒绝使用独立短事务
当业务事务已回滚后,系统 SHALL 使用独立短事务写入 `failed``denied` Audit Event。失败审计二次写入失败 MUST 保留原业务错误,并 MUST 记录 critical 日志和可监控指标;系统 MUST NOT 使用裸 goroutine 执行审计写入。
#### Scenario: 资金业务回滚后的失败审计
- **WHEN** 钱包扣款因余额不足或并发条件失败而回滚
- **THEN** 系统在独立短事务中记录失败或拒绝事件,且不会创建成功资金事实
#### Scenario: 失败审计自身不可用
- **WHEN** 原业务失败且独立短事务也无法写入审计事件
- **THEN** 接口仍返回原业务错误,同时输出包含 request/correlation 标识的 critical 日志和指标
### Requirement: 批量根事件与资源子事件
批量操作 SHALL 写入一条批次根事件,记录操作者、输入条件或任务、总数、成功数、失败数和总体结果;每个实际发生变化的资源 MUST 写入可独立查询的子事件。未处理或未命中的资源 MUST NOT 伪造成功子事件。部分成功时根事件 MUST 使用 `partial`
#### Scenario: 批量分配设备并连带绑定卡
- **WHEN** 批量任务成功分配部分设备且每台设备包含多张绑定卡
- **THEN** 根事件记录批量统计,每台实际变化的设备产生子事件,子事件同时关联该设备实际连带变化的卡、来源店铺和目标店铺
#### Scenario: CSV 批量购包部分成功
- **WHEN** CSV 资产套餐订购中部分行创建订单成功、部分行失败
- **THEN** 根事件结果为 `partial`,成功资产分别生成关联订单、钱包流水和套餐权益的子事件,失败行只保存必要失败事实且不伪造业务成功资源
### Requirement: 跨请求和异步链路关联
系统 SHALL 使用 `request_id` 关联同一 HTTP 请求内的事件,使用 `correlation_id` 关联跨请求、审批、Integration Log、Outbox、Asynq 和 Domain Ledger 的业务链路,使用 `parent_event_id` 表达批次父子或后续步骤因果关系。异步载荷 MUST 传递已有链路标识或建立可回查的稳定业务关联。
#### Scenario: 支付链路跨越外部回调
- **WHEN** 用户创建支付后,支付渠道稍后回调并触发钱包或订单终态
- **THEN** 创建支付、Integration Log、回调处理、订单终态和资金事件可通过 correlation 或稳定业务单号组成同一链路
### Requirement: 安全凭据写入前删除
平台审计数据 SHALL 保留完整业务字段而不做展示脱敏但密码、操作密码、验证码、Access/Refresh Token、Secret、私钥、支付密钥、回调 Token、EncodingAESKey、Authorization、Cookie 和完整签名 URL 等系统安全凭据 MUST 在进入持久化 Writer 前删除。Action Registry 和 Resource Registry MUST 为涉及配置、请求摘要和前后数据的动作声明禁止字段;禁止字段 MUST 不得出现在事件、资源快照、前后数据或 metadata 中。
#### Scenario: 更新支付配置
- **WHEN** 平台账号更新微信、富友或支付宝配置
- **THEN** Audit Event 可以保存配置 ID、名称、渠道、状态和 `credentials_configured` 等业务事实,但不保存密钥、私钥、证书正文或回调凭据
#### Scenario: 修改账号密码
- **WHEN** 账号密码修改成功或失败
- **THEN** 事件记录目标账号、动作和结果,但 before、after 与 metadata 均不包含原密码、新密码或密码散列

View File

@@ -0,0 +1,122 @@
## ADDED Requirements
### Requirement: 以当前代码重新建立审计覆盖基线
实施前系统团队 MUST 重新扫描当前仓库的 HTTP RouteSpec、Handler、Application、旧 Service、Asynq Worker、定时任务、外部回调、GORM Model、Outbox 消费者、Integration Log 接入点和旧审计 Writer并 SHALL 形成可复核的逐入口覆盖清单。清单 MUST 覆盖平台、代理、企业、个人客户、Open API 和系统自动入口,并 MUST 为每项记录代码入口、业务领域、动作、操作者来源、资源、事务边界、结果策略以及 Audit Event、Domain Ledger、Integration Log、Outbox 的使用决定或明确 N/A 理由。
#### Scenario: 发现七月后新增的定时任务
- **WHEN** 当前代码包含在线充值恢复、订单过期、告警、企微审批恢复、数据清理、通知清理、套餐临期提醒或流量落盘等计划任务
- **THEN** 新覆盖清单按当前代码逐项登记,而不是沿用旧清单中的定时任务数量
#### Scenario: 普通查询入口分类
- **WHEN** 扫描发现列表、详情、统计或其他纯读取入口
- **THEN** 清单将普通读取的 Audit Event 标记为 N/A 并写明理由,且不将其伪装为业务动作
- **AND** 对 Action Registry 标记的敏感读取另行登记读取审计、目标资源和失败关闭策略
### Requirement: 历史清单仅作遗漏参考
七月旧 490 项覆盖清单、旧 22 张 Ticket 和历史自动分类 MUST NOT 被直接视为本 Change 的现行实施契约。系统团队 SHALL 将其作为遗漏比对材料,并 MUST 对当前代码重新进行业务语义分类。自动扫描结果 MUST 经过业务和研发复核MUST 排除依赖注入 Setter、纯查询、装配方法和没有业务副作用的技术入口等误报。
#### Scenario: 自动扫描把 Setter 识别为写操作
- **WHEN** 候选清单包含 `SetXxx` 依赖注入方法或类似装配入口
- **THEN** 评审将其标记为非生产业务动作并排除,且不会为其生成 Action Registry 项
#### Scenario: 旧清单缺少新入口
- **WHEN** 当前路由、Worker 或 Scheduler 中存在旧 490 项清单没有的入口
- **THEN** 新清单补充该入口并按当前业务事实分类,旧统计数字不得覆盖当前扫描结果
### Requirement: 领域与资源盘点完整性
覆盖清单和 Resource Registry MUST 至少核验账号权限、店铺企业、个人客户、卡设备与卡槽绑定、资产分配与企业授权、换货、套餐与套餐权益、订单支付退款充值、代理及资产钱包、佣金提现、审批与企微、配置与运营商、导入批量、导出、敏感读取、通知、轮询监控、Integration Log、Outbox 和异步任务。每个领域 MUST 明确其一等资源、稳定标识快照、多资源关系、资金或状态 Domain Ledger 以及明确不迁移的旧代码范围。
#### Scenario: 盘点设备领域
- **WHEN** 团队评审设备写入口
- **THEN** 盘点同时覆盖设备、绑定卡、卡槽关系、分配记录、企业授权、套餐权益、钱包和个人客户绑定,而不是只登记设备主表
#### Scenario: 盘点资金领域
- **WHEN** 团队评审订单、退款、充值、佣金或提现入口
- **THEN** 清单明确钱包、唯一交易流水、业务单据和 Outbox 的权威事实边界Audit Event 不替代 Domain Ledger
### Requirement: 受评审的 Action Registry
每个需要审计的完整业务用例 MUST 对应稳定 Action Registry 条目。条目 MUST 声明动作编码、中文名、领域、风险等级、允许来源、主要及受影响资源、资源角色、必须快照字段、前后数据策略、禁止字段、成功事务策略、失败策略和外部可见性。新增或修改非查询业务入口时 MUST 同步更新 Registry 与覆盖清单;未登记动作 MUST 在验证门禁中失败,不得运行时静默使用任意字符串。
#### Scenario: 新增设备卡槽切换动作
- **WHEN** 新增或迁移设备当前卡切换用例
- **THEN** Registry 明确设备、旧卡、新卡和两个绑定关系的资源角色与快照要求,并由覆盖门禁校验该入口已登记
#### Scenario: 动作决定不记录 Audit Event
- **WHEN** 业务评审确认某入口是普通查询或不产生审计事实
- **THEN** 覆盖清单记录明确且可复核的 N/A 理由,而不是留空或删除该入口
### Requirement: 按完整纵向用例渐进切换
审计接入 SHALL 按可独立验证的纵向业务用例执行 `expand → migrate → contract`MUST NOT 按“先全仓建表、再全仓改 Service、最后统一切换”的水平分层方式迁移。每个迁移单元 MUST 标明其主架构通道为复杂写、简单写、Query、Infrastructure 或 Application + Port/AdapterMUST 收口该用例的完整事务、不变量、资源关联、成功/失败审计和查询可见性,并 MUST 明确本单元不迁移的旧范围。
#### Scenario: 迁移钱包扣款用例
- **WHEN** 钱包扣款纵向切片进入迁移
- **THEN** 同一切片完成 Domain/Application 资金规则、钱包和流水持久化、同事务 Audit Event、必要 Outbox、失败短事务及可查询验证不把审计留给后续水平任务
#### Scenario: 迁移简单配置写入
- **WHEN** 单表配置更新没有复杂状态机或金额不变量
- **THEN** 使用 Application 事务脚本接入 Audit Writer不为审计形式强行创建聚合或多余接口
#### Scenario: 迁移只读调查视角
- **WHEN** 实现平台审计或资源时间线查询
- **THEN** 使用 `Handler → Query → GORM/DTO`,不让查询经过聚合根或修改状态
### Requirement: 单个纵向切片的切换门禁
每个纵向用例只有在以下条件全部满足后 SHALL 停止旧 Writer真实业务成功事件可查、失败或拒绝事件可查、主要和受影响资源时间线均可定位该操作、事务策略符合风险等级、安全凭据不落库、Action Registry 与覆盖清单已更新、目标用例的旧 Writer 调用归零。未满足任一条件时 MUST 不得宣称该切片切换完成。
#### Scenario: 换货切片准备停写旧资产日志
- **WHEN** 换货用例计划停止旧资产 operation log
- **THEN** 门禁验证换货单、旧新卡或设备、设备绑定卡、钱包、套餐权益和客户绑定均按实际变化可追踪,并确认该用例不再调用旧 Writer
#### Scenario: 批量任务仅记录根事件
- **WHEN** 批量分配或购包任务只有批次根事件而单资源时间线没有子事件
- **THEN** 切换门禁失败,旧 Writer 不得在该用例中停写
### Requirement: 旧 Operation Log 的前向停写与历史保留
统一 Audit Event 切换完成的用例 MUST 停止向账号和资产 operation log 新增记录。旧表 SHALL 原样保留MUST NOT 在线回填、转换或接入新审计中心新审计中心的完整时间范围从切换点开始。手动轮询日志继续承担任务运行状态和历史事实MUST NOT 因统一审计切换而提前停写。
#### Scenario: 查询切换前账号历史
- **WHEN** 平台查询统一审计上线前的账号操作
- **THEN** 新审计中心不返回旧账号 operation log 记录
- **AND** 旧表继续原样保留,不为其创建 Audit Event 或 Event Resource
#### Scenario: 手动轮询任务仍在运行
- **WHEN** 统一 Audit Event 已记录人工触发动作
- **THEN** 手动轮询日志仍保存任务运行状态和结果Audit Event 只表达谁触发了任务及业务关联
### Requirement: 旧 Writer 归零验证
项目 MUST 维护旧账号审计 Writer、旧资产审计 Writer、裸 goroutine 审计调用和直接旧表写入的显式清单。最终 contract 阶段 MUST 通过静态扫描与真实业务验证证明已迁移用例的旧调用归零;既有旧资产历史查询和必要的手动轮询运行写入 MUST 被明确白名单化,且旧历史查询不得进入新审计路由或统一 Query。
#### Scenario: 发现旧账号 Writer 的异步调用
- **WHEN** 迁移用例仍通过裸 goroutine 或旧 account audit service 写入账号 operation log
- **THEN** 最终切换门禁失败并定位该调用点
#### Scenario: 仅保留独立旧表查询
- **WHEN** 代码只通过既有独立历史入口读取旧 operation log
- **THEN** 静态门禁允许该只读依赖,但禁止新审计 Query 依赖旧表,并禁止 Create、Update 或直接表写入
### Requirement: 发布总门禁
正式发布前 MUST 满足当前入口清单无未分类项所有资金、权限、关键配置、敏感读取和人工状态变更均有事务与失败策略Action Registry 和 Resource Registry 与代码一致;多资源、批量、设备卡槽、换货和外部回调场景通过验收;平台、代理和企业身份边界符合契约;安全凭据不落库;旧 Writer 按已迁移范围归零;查询分页、索引和无 N+1 证据满足项目性能目标;每日归档、月度最终复核、清理阻断和在线窗口语义通过验收;第一阶段没有面向用户的审计导出、归档查询/恢复或业务删除接口。
#### Scenario: 存在未分类生产写入口
- **WHEN** 覆盖清单与当前代码比对发现一个未分类的生产非查询入口
- **THEN** 发布总门禁失败,直到该入口完成业务评审并登记 Audit Event 或 N/A 决定
#### Scenario: Integration Log 调查接口包含恢复操作
- **WHEN** OpenAPI 或路由检查发现 Integration Log 查询中心提供重试、补偿、绑定或恢复写接口
- **THEN** 第一阶段发布门禁失败
### Requirement: 切换监控与前向修复
切换期间系统 MUST 监控 Audit Event 写入成功率和延迟、失败短事务二次失败、未知动作或资源、凭据删除命中、批量根子事件数量差异、归档/清理状态以及旧 Writer 调用。已提交的 Audit Event MUST 不因回滚部署、人工数据修复或业务纠错而删除;错误审计事实 MUST 通过更正事件或前向迁移修复。只有 Retention Worker 可在归档完整性门禁通过后删除 PostgreSQL 中已归档的上月 Audit/Integration 数据。发布回滚 MUST 只影响后续流量路由MUST 保留数据库当前月 Audit Event、Integration Log 和全部 Domain Ledger不得删除或回滚已验证完成的对象存储备份。
#### Scenario: 审计失败率超过发布阈值
- **WHEN** 切换后关键成功审计写入失败率或延迟超过发布阈值
- **THEN** 发布流程停止扩大迁移范围,并按失败用例前向修复;已经提交的审计和业务事实不被删除
#### Scenario: 归档校验未完成时到达月初
- **WHEN** 上月任一自然日的 Audit 或 Integration 缺少成功 manifest或对象数量、大小、SHA-256 与数据库最终内容不一致
- **THEN** 系统阻止整月在线清理并告警
- **AND** 新业务审计继续正常写入,不因对象存储故障回滚
#### Scenario: 批量根子事件数量异常
- **WHEN** 批量根事件的成功计数与可查询子事件数量不一致
- **THEN** 监控告警并阻止该纵向切片进入 contract 阶段

View File

@@ -0,0 +1,193 @@
## 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** 不返回成功空列表、不扫描对象存储

View File

@@ -0,0 +1,88 @@
## ADDED Requirements
### Requirement: 按完整自然日压缩归档审计相关日志
系统 SHALL 复用现有 S3 兼容对象存储、Worker 和 Asynq Scheduler`Asia/Shanghai` 将前一完整自然日的 Audit Event 及全部 Event Resource、Integration Log 分别生成 UTF-8 `JSON Lines + gzip` 冷归档。归档 MUST 使用半开时间区间MUST NOT 扫描或混入 Access Log、Domain Ledger、Outbox、Asynq 任务、手动轮询或旧 operation log。Access Log SHALL 继续使用应用服务器本地文件及现有 Lumberjack 轮转与保留策略MUST NOT 被本能力上传到对象存储。对象存储失败 MUST 只使归档任务重试和阻止后续清理MUST NOT 阻止新的业务审计正常落库。
#### Scenario: 每日归档前一天日志
- **WHEN** `Asia/Shanghai` 新自然日的归档任务执行
- **THEN** 系统只归档前一天 `[00:00:00, 次日 00:00:00)` 创建的目标日志
- **AND** Audit 每行包含一个事件及其完整资源数组Integration 每行包含一条结构化持久化记录
#### Scenario: 对象存储暂时不可用
- **WHEN** 上传归档对象或读取对象 metadata 失败
- **THEN** 任务按既有 Asynq 重试策略重试并记录失败状态、日志和指标
- **AND** Audit Event 与 Integration Log Writer 不调用对象存储且继续服务业务请求
### Requirement: 归档对象必须具有可验证 manifest
每个归档对象 MUST 具有 manifest至少记录 `schema_version`、数据源、归档日期、时区、时间范围、实例 ID、事件/资源或记录数量、未压缩与压缩字节数、对象 Key、SHA-256、revision、生成时间和状态。系统 SHALL 使用 `tb_log_archive_run` 保存轻量运行 ledger并 MUST 以 `source + archive_date + instance_id + schema_version` 保证调度幂等;该表不得保存日志正文。
#### Scenario: 重复执行相同日归档
- **WHEN** 同一数据源、日期、实例和 schema version 的任务被重复投递
- **THEN** 系统校验并复用内容一致且已经成功的对象
- **AND** 不重复创建相同事实或把重复执行计为新归档日
#### Scenario: 同一对象 Key 对应不同内容
- **WHEN** 当前数据库数量或 SHA-256 与已成功 manifest 不一致
- **THEN** 系统创建不可变的新 revision 并保留旧对象
- **AND** 不静默覆盖原对象或直接宣称本次归档成功
### Requirement: Integration Log 月度清理前必须形成最终快照
由于 Integration Log 可在创建后从 pending 更新为终态,系统 MUST 在月初清理前使用数据库当前内容复核上月每日归档。内容发生变化时 MUST 先创建新的最终 revision 并通过完整性校验;仍处于可变状态或无法形成最终快照的记录 MUST 阻止该月清理。
#### Scenario: 上月 pending 记录在本月完成
- **WHEN** 某 Integration Log 的每日归档版本为 pending但月初复核时数据库记录已经完成
- **THEN** 系统生成包含最终状态的新 revision 并更新成功 manifest
- **AND** 只有最终 revision 验证通过后才允许清理数据库记录
#### Scenario: 上月记录仍无法最终确认
- **WHEN** 月初复核发现上月 Integration Log 仍处于可变状态或归档内容无法与数据库一致
- **THEN** 系统阻止整月清理并告警
- **AND** 不删除该记录或其他 PostgreSQL 上月审计数据
### Requirement: 月初清理必须以整月归档完整性为硬门禁
每月初系统 SHALL 在上月最后一天归档成功后检查上月全部自然日的 Audit 和 Integration 归档。只有 Audit Event 数量、Event Resource 数量、Integration 最终内容、对象存在性、对象大小、SHA-256、时间范围和 manifest 状态全部一致,且没有 pending 或 failed 任务时Retention Worker 才可物理删除 PostgreSQL 上月 Audit Event、Event Resource 和 Integration Log 数据。对象存储中的归档对象 MUST 长期保留且不得被该任务删除。Access Log 不参与归档或数据库删除门禁。任一检查失败 MUST 阻止整月数据库删除并产生 critical 日志和指标。
#### Scenario: 上月缺少一天 Audit 归档
- **WHEN** 月度门禁发现某日 Audit manifest 缺失或 Event Resource 数量不一致
- **THEN** 系统不删除上月任何 Audit Event、Event Resource 或 Integration Log
- **AND** 后续调度继续补档并重新执行门禁
#### Scenario: 月度门禁全部通过
- **WHEN** 上月全部 Audit/Integration 归档对象和 manifest 与数据库一致
- **THEN** Retention Worker 使用 GORM、`created_at` 索引和有界批次对该完整自然月执行数据库物理 `DELETE`
- **AND** Event Resource 与 Event 作为同一事实集合清理,任务中断后可从 ledger 安全继续
#### Scenario: 禁止用软删除代替物理删除
- **WHEN** Retention Worker 清理 PostgreSQL 上月 Audit/Integration 数据
- **THEN** 对应表记录从数据库中真实移除
- **AND** 不新增或更新 `deleted_at`、archive status、隐藏标记也不迁移到另一张 PostgreSQL 历史表
### Requirement: 清理权限和清理事实必须隔离
业务 Handler、Query、Writer 和 Repository MUST NOT 获得审计删除能力。只有 Retention Worker 的独立最小权限入口可按已通过门禁的月份物理删除 PostgreSQL Audit/Integration 数据。Audit Event 和 Event Resource Model MUST NOT 包含 `gorm.DeletedAt`Integration Log 现有无软删除字段的模型保持不变。清理任务 MUST 以 `retention_worker` 系统身份在当前月写 Audit Event记录目标月份、数据源数量、manifest Key 和结果,不得把清理事件写回被清理月份。
#### Scenario: 调用方尝试删除审计记录
- **WHEN** 平台账号或其他业务调用方尝试删除 Audit Event、Event Resource、Integration Log 或归档对象
- **THEN** 系统不存在对应业务 API 或 Repository 方法
#### Scenario: 月度数据库清理完成
- **WHEN** Retention Worker 完成 PostgreSQL 上月 Audit/Integration 数据删除
- **THEN** 当前月产生一条可调查的系统 Audit Event
- **AND** 该事件不属于本次已清理时间窗口
### Requirement: 归档历史不再支持在线调查
清理后的 Audit Event 与 Integration Log SHALL 只保留在对象存储。第一阶段 MUST NOT 提供归档下载、对象存储扫描、跨冷热存储查询或恢复接口。所有审计与 Integration 列表、时间线和总览 DTO MUST 返回 `retention{online_from, archived_before, timezone}`;显式时间范围全部位于归档区间或跨越在线边界时 MUST 返回稳定的已归档错误,不得返回看似完整的空结果或部分结果。
#### Scenario: 查询已清理月份的资源时间线
- **WHEN** 调用方显式查询早于 `online_from` 的资源活动或 Audit 时间线
- **THEN** 系统返回“数据已归档且第一阶段不支持在线查询”的稳定错误
- **AND** 不扫描对象存储或返回成功空列表
#### Scenario: 查询范围跨越冷热边界
- **WHEN** 调用方提交的开始和结束时间同时覆盖已归档区间与在线区间
- **THEN** 系统拒绝部分查询并返回当前在线窗口
- **AND** 调用方可缩小到 `online_from` 之后重新查询
#### Scenario: 查询当前在线月份
- **WHEN** 查询条件完全位于当前在线窗口
- **THEN** 系统按既有调查契约返回 PostgreSQL 在线结果和 retention 元数据
- **AND** 不访问对象存储

View File

@@ -0,0 +1,159 @@
## ADDED Requirements
### Requirement: 平台提供只读外部集成调查中心
系统 MUST 为已认证的平台账号提供只读 Integration Log 调查能力,平台账号不应用店铺或企业数据范围过滤,第一阶段不设置细粒度权限码;代理、企业和个人客户 MUST NOT 访问该调查能力。所有成功响应 MUST 使用统一 `{code, msg, data, timestamp}` 格式。
#### Scenario: 平台账号访问调查中心
- **WHEN** 已认证的平台账号查询 Integration Log 总览、列表或详情
- **THEN** 系统返回平台范围内符合查询条件的完整业务摘要,且不应用店铺或企业数据范围过滤
#### Scenario: 非平台主体尝试访问
- **WHEN** 代理、企业或个人客户直接请求平台 Integration Log 调查接口
- **THEN** 系统统一拒绝访问,且不泄露目标记录是否存在
### Requirement: 外部交互异常总览
系统 MUST 提供受时间范围约束的 Integration Log 总览至少返回交互总数、结果分布、提供方分布、入站与出站分布、异常数量、结果未知数量、陈旧待处理数量、本地状态变化数量、平均耗时、P95 耗时和按时间分桶的趋势。总览 MUST 区分实际成功、结果未知、明确失败、处理中和未发送终态,不得把 `completed``ignored``merged``rate_limited``cancelled` 计为外部请求成功。
#### Scenario: 查询指定时间范围总览
- **WHEN** 平台账号提交合法的开始时间、结束时间和时间粒度
- **THEN** 系统在该时间范围内返回固定结构的汇总、分类计数和趋势数据,并为每个稳定结果编码返回中文名称及派生类别
#### Scenario: 区分未发送终态与成功
- **WHEN** 时间范围内同时存在 `success``completed``merged``rate_limited` 记录
- **THEN** 系统仅把 `success` 计入实际成功,并把其余记录计入对应的未发送或提前完成分类
### Requirement: Integration Log 组合筛选列表
系统 MUST 提供按创建时间和主键稳定倒序的分页列表,支持组合筛选 `integration_id`、provider、direction、operation、原始 result 或派生结果类别、external_id、resource_type 与 resource_id、resource_type 与 resource_key、trigger_source、trigger_scene、trigger_series、state_changed、http_status、provider_code、request_id、correlation_id 和创建时间范围。筛选条件 MUST 使用精确匹配或受控枚举,不得提供任意 SQL、任意 JSONPath 或任意 JSONB 字段搜索。
#### Scenario: 组合查询外部失败记录
- **WHEN** 平台账号同时指定 provider、operation、失败派生类别、资源和时间范围
- **THEN** 系统仅返回同时满足全部条件的记录,并为列表项返回稳定编码、中文名称、主要资源、结果、耗时、状态变化、请求标识、关联标识和发生时间
#### Scenario: 查询结果为空
- **WHEN** 合法筛选条件没有命中任何记录
- **THEN** 系统返回成功的空分页结果,而不是资源不存在错误
#### Scenario: 拒绝无界或非法筛选
- **WHEN** 查询时间范围、页码、每页数量或枚举值超过接口约束
- **THEN** 系统返回统一参数错误,且不执行无界全表查询
### Requirement: 使用稳定 integration_id 查询结构化详情
系统 MUST 使用业务稳定的 `integration_id` 而非数据库自增 ID 定位单条详情,并按 identity、resource、trigger、result、content、linkage、timestamps 和 attempts 分区返回结构化 DTO。详情 MUST 包含可用的请求摘要、响应摘要、metadata、正文安全摘要、HTTP 状态、渠道结果码、可读安全结果摘要、耗时、本地状态变化、人工核对说明、request_id、correlation_id 和关联 Audit Event ID。
#### Scenario: 查询单条外部交互详情
- **WHEN** 平台账号使用存在的 `integration_id` 查询详情
- **THEN** 系统返回该记录的结构化详情及 provider、direction、operation、result 的稳定编码和中文名称,不要求前端解析数据库 Model 或任意 JSON 才能识别基础语义
#### Scenario: 稳定标识不存在
- **WHEN** 平台账号使用不存在的 `integration_id` 查询详情
- **THEN** 系统返回统一资源不存在错误,且不回退使用数据库自增 ID 猜测记录
### Requirement: Integration Log 查询入参必须有明确来源
总览的时间范围、粒度及聚合筛选和列表的组合筛选 SHALL 来自调查人员输入、通知目标或其他调查视角携带的稳定值provider、direction、operation、result 等枚举选项 MUST 来自后端稳定常量。详情 `integration_id` MUST 来自 Integration 列表、通知目标或事件/request/correlation 时间线节点,也 MAY 由调查人员粘贴稳定 Integration ID系统 MUST NOT 使用数据库自增 ID、相似资源或相近时间猜测目标记录。
#### Scenario: 从列表打开详情
- **WHEN** 调查人员选择 Integration 列表中的一条记录
- **THEN** 前端使用列表返回的稳定 `integration_id` 请求详情
- **AND** 后端不要求前端提供数据库主键或重复提供资源条件
#### Scenario: 从其他调查视角跳转
- **WHEN** 事件、request 或 correlation 时间线节点关联一条外部交互
- **THEN** 节点返回可跳转的稳定 `integration_id`,前端据此打开详情
#### Scenario: 查询身份来自认证上下文
- **WHEN** 调用方请求 Integration Log 总览、列表或详情
- **THEN** Handler 从认证上下文判断其是否为平台身份
- **AND** 不接受 query、path 或 body 传入平台、店铺或企业范围
#### Scenario: 从通知目标打开 Integration 详情
- **WHEN** `GET /api/admin/notifications/{id}/target` 返回 `available=true``target_type=integration_log` 和非空 `target_key`
- **THEN** 前端将 `target_key` 原样作为 `integration_id` 调用 Integration 详情
- **AND** 不直接解析通知列表的 `ref_type/ref_id/ref_key` 或按资源与时间重新搜索
#### Scenario: 通知目标不可用
- **WHEN** 通知目标解析返回 `available=false` 或缺少 `target_key`
- **THEN** 前端只展示通知正文,不显示 Integration 详情跳转
### Requirement: 尝试序列只使用显式技术序列
系统 MUST 仅在记录具有相同非空 `trigger_series` 时将其组织为同一技术尝试序列,并按 attempt、发生时间和主键稳定排序。可重试或分阶段外呼的新接入 MUST 写入稳定 `trigger_series` 和单调递增的 attempt缺少 `trigger_series` 的历史记录 MUST 作为单次交互展示,系统不得仅因 provider、operation、资源或时间接近而猜测其属于同一重试序列。
#### Scenario: 展示显式尝试序列
- **WHEN** 用户查询的记录带有 `trigger_series`,且存在同序列的多个尝试
- **THEN** 详情按 attempt 和时间返回完整尝试序列,并逐条保留是否发送、结果、耗时和状态变化
#### Scenario: 历史记录缺少序列标识
- **WHEN** 用户查询的历史记录没有 `trigger_series`
- **THEN** 系统只返回当前单次交互,不把相同资源或相同 operation 的相邻记录拼成重试序列
### Requirement: 业务关联链路与技术重试保持不同语义
系统 MUST 将 `correlation_id` 解释为跨请求、异步任务、外部交互和业务事实的业务链路标识,将 `trigger_series + attempt` 解释为一次外部操作的技术尝试序列。按 correlation_id 查询时 MUST 返回相关外部交互节点,但 MUST NOT 将这些节点统一标记为重试;跨 Audit Event、Outbox、Asynq 和 Domain Ledger 的完整时间线由统一审计调查 Query 组合Integration Log Query 只提供外部交互节点。
#### Scenario: 同一业务链路包含不同外部操作
- **WHEN** 同一 correlation_id 下存在预下单、回调和查单等不同 operation
- **THEN** 系统按业务发生时间展示相关交互并保留各自 operation不把回调或查单描述为预下单重试
#### Scenario: 同时存在业务链路和尝试序列
- **WHEN** 一条业务链路中的某个 operation 具有多个显式 trigger_series 尝试
- **THEN** 系统既保留 correlation_id 的业务链路关系,也在该 operation 内单独展示技术尝试序列
### Requirement: 调查中心不承担恢复和导出
Integration Log 调查接口 MUST 只有读取能力。第一阶段 MUST NOT 提供重试、补偿、结果确认、外部单号绑定、人工恢复、状态修改、记录删除或导出接口。`recovery_strategy` 仅作为人工核对说明展示,不得被解释为可执行命令。
#### Scenario: 查看结果未知记录
- **WHEN** 平台账号查看 result 为 `unknown` 的详情
- **THEN** 系统展示已记录的人工核对说明,但响应中不包含恢复动作地址、可执行命令或写操作按钮契约
#### Scenario: 尝试调用恢复或导出能力
- **WHEN** 调用方尝试通过审计中心执行 Integration Log 恢复、修改、删除或导出
- **THEN** 系统不存在对应业务路由或统一拒绝请求,且原 Integration Log 不发生变化
### Requirement: 安全凭据不得进入或离开 Integration Log
平台可以查看 Integration Log 中已经安全筛选的完整业务摘要但密码、操作密码、验证码、Access Token、Refresh Token、Secret、私钥、回调 Token、EncodingAESKey、Authorization、Cookie、签名 URL、支付密钥、完整加密回调正文和其他系统安全凭据 MUST 在写入前删除,查询接口 MUST NOT 返回这些内容。原始第三方错误正文 MUST NOT 直接进入 provider_message调用方必须提供有界、可读且不含凭据的业务结果摘要。
#### Scenario: 请求摘要包含业务字段和安全凭据
- **WHEN** 外部调用摘要同时包含普通业务字段和系统安全凭据
- **THEN** 系统保留普通业务字段并在持久化前删除安全凭据,详情接口只返回持久化后的安全摘要
#### Scenario: 入站回调包含完整正文
- **WHEN** 系统接收运营商、支付或企业微信回调
- **THEN** Integration Log 只保存受控业务摘要、正文大小和安全 hash不通过调查接口返回完整原始或解密正文
### Requirement: 历史 Integration Log 缺口必须显式兼容
系统 MUST 保留并查询现有 Integration Log不在线伪造回填缺失的 trigger_series、correlation_id、资源 ID 或可读 provider_message。查询 DTO MUST 对无法可靠解析的历史字段返回明确的数据完整性标识;对使用现有确定性 ICCID hash 保存 resource_key 的运营商历史记录,资源查询 MUST 支持按相同受控算法定位,但 MUST NOT 向调用方暴露 hash 作为业务标识。已保存为不可逆摘要的 provider_message MUST 原样标识为历史摘要,不得生成虚假的可读原文。历史 `request_summary/response_summary/metadata` MUST 在响应前按字段白名单和统一凭据删除规则再次清理MUST NOT 直接透传旧 JSON。
#### Scenario: 查询只有 ICCID hash 的历史运营商记录
- **WHEN** 平台账号使用合法 ICCID 查询资源外部交互轨迹,且历史记录只保存确定性 ICCID hash
- **THEN** 系统使用受控兼容规则命中该记录,并把资源解析状态标记为历史兼容,不把 hash 返回为 ICCID
#### Scenario: 历史链路字段缺失
- **WHEN** 历史记录缺少 correlation_id、trigger_series 或可靠资源 ID
- **THEN** 系统仍返回现有事实并标明对应关联能力受限,不猜测链路、重试次数或资源关系
#### Scenario: 历史 JSON 含有未覆盖的安全凭据
- **WHEN** 历史 Integration Log 摘要含有 Token、Secret、Authorization、Cookie、签名 URL 或其他禁止字段
- **THEN** Query 在返回前删除禁止字段并保留其余完整业务摘要
- **AND** 不直接序列化历史 JSON 到响应
### Requirement: Integration Log 查询必须分页并命中受控索引
列表 MUST 默认每页 20 条、最大 100 条,并要求受控时间范围;总览 MUST 限定时间范围和固定聚合维度。实现 MUST 使用创建时间、结果、provider、external_id、资源、trigger_series、request_id、correlation_id 和 audit_event_id 的受控索引完成主要查询,不得逐条补查产生 N+1不得为第一阶段的摘要展示增加任意 JSONB GIN 搜索。
#### Scenario: 查询高频轮询历史
- **WHEN** 平台账号查询包含大量 Gateway 高频记录的合法时间窗口
- **THEN** 系统使用分页和匹配的时间或筛选索引返回结果,单页查询不加载窗口外全部记录,也不逐条查询关联基础信息
#### Scenario: 深分页或超大时间窗口
- **WHEN** 调用方请求超过允许范围的页码、每页数量或时间窗口
- **THEN** 系统返回统一参数错误,引导调用方缩小时间窗口,而不是执行高成本扫描
### Requirement: Integration 调查只覆盖在线留存窗口
Integration overview、列表、详情和关联时间线 MUST 仅查询 PostgreSQL 在线数据,列表和总览 DTO MUST 返回 `retention{online_from, archived_before, timezone}`。月初清理后的上月记录只保留在对象存储,第一阶段 MUST NOT 提供归档下载、对象存储扫描、冷热联合查询或恢复能力。显式时间范围早于或跨越 `online_from` 时 MUST 返回稳定的已归档错误,不能返回成功空结果或部分结果。
#### Scenario: 查询已归档的外部交互月份
- **WHEN** 平台账号查询的 Integration 时间范围全部早于 `online_from`
- **THEN** 系统返回数据已归档和当前在线窗口
- **AND** 不执行 PostgreSQL 无效扫描或对象存储查询
#### Scenario: 当前月份外部交互调查
- **WHEN** 查询范围完全位于在线窗口
- **THEN** 系统返回符合条件的 Integration Log 与 retention 元数据
- **AND** 详情和尝试序列仍遵循现有稳定 ID 与显式 `trigger_series` 契约

View File

@@ -0,0 +1,125 @@
## ADDED Requirements
### Requirement: 代理和企业必须使用独立的安全资源活动接口
系统 SHALL 为代理和企业提供独立于平台内部审计中心的只读资源活动接口:代理使用 `GET /api/admin/agent/resource-activities/{resource_type}/{identifier}`,企业使用 `GET /api/admin/enterprise/resource-activities/{resource_type}/{identifier}`。两者 MUST 使用 `page/page_size` 和专用安全投影 DTO响应资源摘要 MUST 包含 `resource_type/resource_id/resource_key/display_name`,活动项 MUST 仅包含外部动作编码/中文名、`subject_summary`、Registry 白名单约束的 `subject_data`、结果、发生时间及允许公开的关联资源摘要。接口响应 SHALL 使用统一的 `{code, msg, data, timestamp}` 格式,且不得通过响应字段、路径命名或错误差异暴露平台内部审计实现。
#### Scenario: 代理查询有权管理的资源活动
- **WHEN** 代理账号查询自己店铺或下级店铺范围内的卡、设备或其他已支持资源
- **THEN** 系统返回该资源允许代理感知的分页活动时间线
- **AND** 默认每页 20 条且每页最多 100 条
#### Scenario: 企业查询已授权资源活动
- **WHEN** 企业账号查询当前企业仍然有效授权的卡或设备
- **THEN** 系统返回该资源允许企业感知的分页活动时间线
#### Scenario: 外部主体无法访问平台审计中心
- **WHEN** 代理或企业账号直接调用平台内部审计接口
- **THEN** 系统统一返回无权限错误
- **AND** 不返回任何平台审计数据
### Requirement: 资源归属必须在后端按真实业务关系校验
系统 MUST 在查询活动事件之前验证目标资源属于当前调用方的数据范围,不得仅依赖前端页面可见性,也不得把缺失的店铺范围解释为不受限制。
#### Scenario: 资源标识来自受权业务入口
- **WHEN** 代理或企业从当前卡、设备等业务列表或详情页打开“活动记录”
- **THEN** 前端使用该页面已有的 `resource_type/identifier` 请求资源活动接口
- **AND** `identifier` 使用 Resource Registry 为该类型声明的业务稳定标识,不由前端猜测内部资源 ID
#### Scenario: 代理从卡资产详情打开活动记录
- **WHEN** 代理调用 `GET /api/admin/assets/resolve/{identifier}` 并得到 `asset_type=card` 与非空 `iccid`
- **THEN** 页面调用 `GET /api/admin/agent/resource-activities/iot_card/{iccid}`
- **AND** 店铺范围由认证上下文提供,不把响应中的 `shop_id` 作为授权凭证传入
#### Scenario: 代理从设备资产详情打开活动记录
- **WHEN** 代理调用资产详情并得到 `asset_type=device` 与非空 `virtual_no`
- **THEN** 页面调用 `GET /api/admin/agent/resource-activities/device/{virtual_no}`
#### Scenario: 企业从受权资产列表打开活动记录
- **WHEN** 企业从 `GET /api/admin/enterprises/{id}/cards``items[].iccid``GET /api/admin/enterprises/{id}/devices``items[].virtual_no` 打开活动记录
- **THEN** 页面分别调用 `GET /api/admin/enterprise/resource-activities/iot_card/{iccid}``GET /api/admin/enterprise/resource-activities/device/{virtual_no}`
- **AND** 后端忽略路由来源中的企业 ID 作为授权证明,始终以认证上下文企业 ID 复核当前有效授权
#### Scenario: 企业不能从平台资产详情进入
- **WHEN** 当前统一资产 resolve 接口禁止企业账号调用
- **THEN** 企业前端不展示依赖该接口的活动入口,也不尝试使用平台资源时间线替代
#### Scenario: 身份范围来自认证上下文
- **WHEN** 代理或企业请求资源活动接口
- **THEN** Handler 从认证上下文取得当前账号、代理店铺范围或企业 ID
- **AND** 不接受 query、path 或 body 传入 shop ID、enterprise ID 或其他主体范围
#### Scenario: 代理资源范围按店铺层级校验
- **WHEN** 代理账号查询资源活动
- **THEN** 系统验证资源当前归属店铺位于该代理自己或下级店铺范围内
#### Scenario: 企业卡归属按有效授权校验
- **WHEN** 企业账号查询某张卡的资源活动
- **THEN** 系统验证企业卡授权记录的 `enterprise_id` 等于当前企业且授权未撤销
#### Scenario: 企业设备归属按有效授权校验
- **WHEN** 企业账号查询某台设备的资源活动
- **THEN** 系统验证企业设备授权记录的 `enterprise_id` 等于当前企业且授权未撤销
#### Scenario: 无权资源不泄露存在性
- **WHEN** 代理或企业查询不存在、不支持或不属于自身范围的资源
- **THEN** 系统返回统一的“无权限操作该资源或资源不存在”错误
- **AND** 响应不得表明资源是否真实存在、是否曾被授权或是否存在内部审计事件
#### Scenario: 活动入口缺少业务标识
- **WHEN** 卡没有 ICCID、设备没有 VirtualNo 或响应没有 Registry 声明的稳定 identifier
- **THEN** 页面隐藏活动入口,不使用内部 ID、名称或其他字段猜测 identifier
### Requirement: 外部活动投影必须按事件可见级别过滤
系统 SHALL 根据 Audit Event 写入时确定的 `internal_only``subject_result``subject_detail` 可见级别生成外部活动投影,不得在查询时把完整内部事件直接序列化后临时删除部分字段。
#### Scenario: 内部事件完全隐藏
- **WHEN** 资源关联事件的可见级别为 `internal_only`
- **THEN** 代理和企业的活动时间线不返回该事件
- **AND** 不以占位记录或数量差异提示该事件存在
#### Scenario: 平台操作仅显示安全结论
- **WHEN** 平台内部操作影响代理或企业资源且可见级别为 `subject_result`
- **THEN** 活动时间线只返回稳定动作名称、安全业务结论、结果和发生时间
- **AND** 不表明该结论是否由平台人工操作、系统任务或内部补偿产生
#### Scenario: 主体自身操作显示允许的业务详情
- **WHEN** 代理或企业自身操作产生 `subject_detail` 事件
- **THEN** 活动时间线返回写入时按 Registry 白名单生成的 `subject_data`、允许公开的关联资源标识和处理结果
- **AND** 仍不返回平台内部字段
#### Scenario: 查询不得从内部字段临时生成主体详情
- **WHEN** 事件没有持久化 `subject_data`
- **THEN** 资源活动 Query 只返回已有安全结论,不读取内部 before/after 后删字段生成详情
### Requirement: 安全资源活动不得泄露内部调查数据
系统 MUST 从代理和企业活动响应中排除平台内部操作者、内部原因、内部备注、风险判断、内部前后数据、Audit Event ID、Integration Log 请求响应及系统安全凭据。
#### Scenario: 平台退款处理对外只展示结果
- **WHEN** 平台完成一笔影响代理资金的退款处理
- **THEN** 代理活动时间线可显示“退款资金已处理”等安全结论及最终结果
- **AND** 不返回平台操作人、人工处理方式、内部备注、风控原因或外部支付响应
#### Scenario: 外部系统交互不直接展示
- **WHEN** 某资源经历外部请求、重试、回调或查询确认
- **THEN** 代理和企业活动时间线只展示允许感知的最终业务结论
- **AND** 不返回第三方错误码、请求摘要、响应摘要、调用次数或 Integration Log 标识
### Requirement: 多资源事件必须进入每个有权资源的活动时间线
系统 SHALL 根据 Audit Event Resource 关联从不同资源入口投影同一业务操作,并 MUST 对每个入口分别执行资源归属和可见性校验。
#### Scenario: 设备换卡在相关资源时间线出现
- **WHEN** 一次换卡事件关联设备、旧卡和新卡且这些资源均属于当前主体范围
- **THEN** 系统在设备、旧卡和新卡的活动时间线中展示相应的安全业务结论
#### Scenario: 部分关联资源不属于当前主体
- **WHEN** 一个事件同时关联当前主体资源和其他主体或平台资源
- **THEN** 系统只返回当前主体有权查看资源对应的安全投影
- **AND** 不返回其他关联资源的内部标识、数量或详情
### Requirement: 主体资源活动只展示在线留存窗口
代理和企业资源活动 DTO MUST 返回 `retention{online_from, archived_before, timezone}`,并 MUST 只查询 PostgreSQL 在线 Audit Event。月初清理后的历史不从对象存储返回显式时间范围早于或跨越在线边界时 MUST 返回稳定的已归档错误,同时仍不得泄露平台内部事件或归档对象信息。
#### Scenario: 代理查看已归档月份的资源活动
- **WHEN** 代理对自身资源提交的显式时间范围早于 `online_from`
- **THEN** 系统返回统一的已归档提示和当前在线边界
- **AND** 不返回冷归档对象 Key、平台事件数量或任何内部调查字段