补充退款列表应当让本店铺的人看见
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m30s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m30s
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-18
|
||||
45
openspec/changes/fix-audit-retention-null-boundary/design.md
Normal file
45
openspec/changes/fix-audit-retention-null-boundary/design.md
Normal file
@@ -0,0 +1,45 @@
|
||||
## Context
|
||||
|
||||
`internal/query/retention` 以聚合查询计算审计与外部交互日志的在线留存边界。PostgreSQL 在没有匹配行时会为 `MAX` 和 `MIN` 返回 `NULL`,而当前扫描目标不能接收空值,导致所有依赖该边界的审计调查接口失败。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 将空聚合结果识别为“尚无边界”,而非数据库错误。
|
||||
- 保持已有已清理边界、最早在线记录和当月兜底的语义。
|
||||
- 修复所有通过同一留存边界查询函数进入的审计接口。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不执行审计物理清理,不改变归档或保留策略。
|
||||
- 不修改数据库结构、迁移记录或历史审计数据。
|
||||
- 不修改 API 路由、权限或响应字段。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 使用可空时间承接 SQL 聚合结果
|
||||
|
||||
留存边界查询使用标准库可空时间值承接 `MAX(range_end)` 与 `MIN(<time-column>)`。仅在值有效时转换为目标时区并作为边界返回。
|
||||
|
||||
拒绝将聚合结果用当前时间或固定时间 SQL `COALESCE`:这会把“尚未清理”误判为已归档,改变响应中的留存语义。
|
||||
|
||||
### 同时覆盖已清理和最早在线两个聚合路径
|
||||
|
||||
`MAX(range_end)` 是本次线上错误入口;`MIN` 在在线表为空时具有相同的空值扫描风险。两个路径共用相同的可空聚合边界,应一次修复。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [风险] 空边界被误判为已清理,错误拒绝历史查询 → 仅在聚合值有效时设置已清理标记与归档边界。
|
||||
- [风险] 修复遗漏其他审计接口 → 保持修改在所有审计查询共用的留存边界函数内。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 修改留存边界的空时间扫描逻辑。
|
||||
2. 格式化并构建 API。
|
||||
3. 仅替换 API 二进制并重启 API Unit。
|
||||
4. 以 `GET /api/admin/audit/events?page=1&page_size=20` 验证无已清理记录时接口不再返回 500。
|
||||
|
||||
### Rollback
|
||||
|
||||
无数据库迁移。若 API 启动或查询异常,覆盖回本次发布前 API 二进制并重启 API Unit。
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
生产环境尚未执行审计物理清理时,留存边界查询中的 `MAX(range_end)` 返回 SQL `NULL`,当前实现无法扫描该值,导致 `GET /api/admin/audit/events` 等审计查询返回服务端错误。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 将审计及外部交互日志留存边界的可空聚合结果作为可空时间处理。
|
||||
- 没有完成物理清理记录时,审计查询继续返回在线数据,且 `archived_before` 保持为空。
|
||||
- 没有在线审计或集成交互数据时,保留现有的当月起始时间兜底边界。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
无。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `operations-audit`: 审计调查在尚无已完成物理清理记录时仍可查询在线审计事实。
|
||||
|
||||
## Impact
|
||||
|
||||
- 代码:`internal/query/retention/retention.go`。
|
||||
- API:修复全局审计事件、时间线、资源活动、风险和资金审计等依赖留存边界的既有查询。
|
||||
- 数据库:无迁移、无数据修复。
|
||||
- 部署:仅需重新构建和发布 API 二进制。
|
||||
@@ -0,0 +1,49 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 审计时间线
|
||||
|
||||
系统 SHALL 支持按事件、操作者、资源、请求、关联标识和资金维度查询已记录的审计事实。新建的 `tb_integration_log` 在已取得稳定审计事件时 SHALL 写入其内部 ID 作为 `audit_event_id`,并且该日志的非空 `integration_id` SHALL 出现在对应事件列表和事件详情的 `investigation_refs.integration_refs` 中;关联生成失败时系统 MUST 保留 Integration Log 并记录可排查告警,且 MUST NOT 按名称、时间或摘要推断关联。历史 Integration Log 不在本要求的回填范围内。审计事件的构造、校验或持久化失败 MUST 记录可关联的结构化错误日志和二次失败记录,且 MUST NOT 改变已通过业务校验的业务操作结果或接口响应。尚无完成物理清理记录时,审计调查接口 MUST 继续查询在线审计事实,且返回的留存边界不得将数据标记为已归档。
|
||||
|
||||
#### Scenario: 审计时间线
|
||||
|
||||
- **GIVEN** 审计事实已存在
|
||||
- **WHEN** 使用对应维度查询
|
||||
- **THEN** 返回匹配的事实与稳定动作编码,不用访问日志替代
|
||||
|
||||
#### Scenario: 事件返回已关联外部交互引用
|
||||
|
||||
- **GIVEN** 一个在线审计事件有 `tb_integration_log.audit_event_id` 指向其内部 ID 的外部交互记录
|
||||
- **WHEN** 查询全局审计事件列表或该事件详情
|
||||
- **THEN** 该事件的 `investigation_refs.integration_refs` 返回该记录的 `integration_id`
|
||||
|
||||
#### Scenario: 事件没有关联外部交互引用
|
||||
|
||||
- **GIVEN** 一个在线审计事件没有稳定关联的外部交互记录
|
||||
- **WHEN** 查询全局审计事件列表或该事件详情
|
||||
- **THEN** `investigation_refs.integration_refs` 返回空数组
|
||||
|
||||
#### Scenario: 新外部交互日志具有稳定审计关联
|
||||
|
||||
- **GIVEN** 系统即将记录一次新的外部调用、入站回调或未发送裁决
|
||||
- **WHEN** 写入对应 Integration Log
|
||||
- **THEN** 该记录保存非空 `audit_event_id`,且其目标 Audit Event 已存在
|
||||
|
||||
#### Scenario: 缺少审计关联时保留外部交互日志
|
||||
|
||||
- **GIVEN** 一次新的外部交互日志没有稳定审计事件关联
|
||||
- **WHEN** 系统尝试写入该 Integration Log
|
||||
- **THEN** 系统持久化该 Integration Log、记录可排查告警,并返回空 `integration_refs`
|
||||
|
||||
#### Scenario: 审计写入失败不阻断业务
|
||||
|
||||
- **GIVEN** 一个业务操作已通过自身输入、权限和状态校验
|
||||
- **WHEN** 该操作的审计事件构造、校验或持久化失败
|
||||
- **THEN** 系统提交或返回该业务操作原本的结果,并以请求关联标识、动作编码和资源标识记录审计失败
|
||||
|
||||
#### Scenario: 尚无物理清理记录时查询在线审计
|
||||
|
||||
- **GIVEN** 审计或外部交互日志尚无完成物理清理的归档运行记录
|
||||
- **WHEN** 调用任一依赖留存边界的审计调查接口
|
||||
- **THEN** 系统返回当前在线数据或空列表
|
||||
- **AND** 响应中的 `archived_before` 为空
|
||||
- **AND** 系统不得因空留存边界返回服务端错误
|
||||
10
openspec/changes/fix-audit-retention-null-boundary/tasks.md
Normal file
10
openspec/changes/fix-audit-retention-null-boundary/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## 1. 留存边界空值处理
|
||||
|
||||
- [x] 1.1 将已清理边界与最早在线记录的聚合结果改为可空时间扫描,仅在结果有效时设置对应边界。
|
||||
- [x] 1.2 保持无已清理记录时 `archived_before` 为空,以及无在线记录时按当月起始时间兜底的现有语义。
|
||||
|
||||
## 2. 验证与发布
|
||||
|
||||
- [x] 2.1 格式化修改文件并构建 API。
|
||||
- [ ] 2.2 验证未执行物理清理时 `GET /api/admin/audit/events?page=1&page_size=20` 不再返回服务端错误。
|
||||
- [ ] 2.3 仅发布并重启 API 二进制;不执行数据库迁移、不改 Worker 配置。
|
||||
Reference in New Issue
Block a user