Files
junhong_cmp_fiber/openspec/changes/refactor-audit-log-persistence/specs/operations-audit/spec.md
邹进 b7a7430cc3
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m8s
feat: 审计动作按落库策略分级写入,系统过程动作改写应用日志
- 审计注册表为可由系统任务、定时任务发起的动作声明落库策略(always/on_success/outcome/log_only/none),未声明按 log_only
- 人工、开放接口、个人客户、外部系统发起的动作始终写审计表
- log_only 写所在服务 app.log(logger=audit),不含快照
- 对接日志不再补写 integration.attempt_started,无上游审计时 audit_event_id 为空
- 新增 audit.persist_overrides 按动作覆盖策略
- 审计覆盖扫描要求机器动作必须声明策略
- 新增 OpenSpec 提案 refactor-audit-log-persistence 与技术方案文档

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:27:24 +09:00

9.2 KiB
Raw Blame History

MODIFIED Requirements

Requirement: 审计时间线

系统 SHALL 支持按事件、操作者、资源、请求、关联标识和资金维度查询已记录的审计事实。新建的 tb_integration_log 在已取得稳定审计事件时 SHALL 写入其内部 ID 作为 audit_event_id;没有上游审计事件时 audit_event_id SHALL 为空,系统 MUST NOT 为补齐关联而另写审计事件,并且该日志的非空 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 该记录的 audit_event_id 为空
  • AND 审计事件表不新增 integration.attempt_started 事件

Scenario: 缺少审计关联时保留外部交互日志

  • GIVEN 一次新的外部交互日志没有稳定审计事件关联
  • WHEN 系统尝试写入该 Integration Log
  • THEN 系统持久化该 Integration Log、记录可排查告警,并返回空 integration_refs

Scenario: 审计写入失败不阻断业务

  • GIVEN 一个业务操作已通过自身输入、权限和状态校验
  • WHEN 该操作的审计事件构造、校验或持久化失败
  • THEN 系统提交或返回该业务操作原本的结果,并以请求关联标识、动作编码和资源标识记录审计失败

Scenario: 尚无物理清理记录时查询在线审计

  • GIVEN 审计或外部交互日志尚无完成物理清理的归档运行记录
  • WHEN 调用任一依赖留存边界的审计调查接口
  • THEN 系统返回当前在线数据或空列表
  • AND 响应中的 archived_before 为空
  • AND 系统不得因空留存边界返回服务端错误

Requirement: 轮询审计事实保留边界

系统 SHALL 仅为人工触发的轮询操作,以及由轮询结果导致的资金或套餐生命周期变化(如套餐到期、激活、退款失效)持久化统一审计事件及资源快照。系统任务发起的轮询同步本身(流量同步、网络状态同步、流量扣减、停复机原因更新)MUST NOT 创建 tb_audit_event 或 tb_audit_event_resource,无论成功、失败或结果未知;其可查证据由采集记录、采集失败记录、停复机记录、日流量记录与应用日志提供。审计调查接口仅返回实际已保留的历史事实。

Scenario: 自动轮询成功但无业务变化

  • GIVEN 一次自动轮询成功且没有改变任何业务状态、有效流量或风险结论
  • WHEN 系统结束该轮询处理
  • THEN 审计事件和审计资源表不新增该次轮询记录

Scenario: 自动轮询同步流量并扣减

  • GIVEN 一次自动轮询读到有效增量并完成扣减
  • WHEN 系统提交该变化
  • THEN 审计事件表不新增流量同步或扣减事件
  • AND 采集记录保存该次读数、扣减判定、增量与原始响应

Scenario: 自动轮询调用失败

  • WHEN 一次自动轮询查流量超时或返回无效响应
  • THEN 审计事件表不新增记录
  • AND 采集失败记录保存该次失败的错误类别、上游返回与原始数据

Scenario: 轮询导致套餐到期

  • GIVEN 轮询或定时任务判定一个套餐已到期
  • WHEN 系统把套餐置为已过期
  • THEN 系统保留套餐到期的审计事实与资源快照

Scenario: 人工触发刷新

  • WHEN 后台账号手动刷新一张卡
  • THEN 系统为该操作写审计事件

ADDED Requirements

Requirement: 审计动作落库策略

系统 SHALL 在统一审计注册表中为每个动作声明落库策略,取值为:总是写(always)、只写成功(on_success)、只写结论(outcome)、只写应用日志(log_only)、不写(none)。

  • 操作者为后台账号、个人客户、OpenAPI 或外部系统时,系统 MUST 按"总是写"处理,不受动作策略限制。
  • 操作者为系统任务或定时任务、且动作未声明策略时,系统 MUST 按"只写应用日志"处理。
  • 审计覆盖扫描 MUST 在存在可由系统任务或定时任务发起、但未声明落库策略的动作时失败。
  • 系统 SHALL 提供按动作编码覆盖落库策略的运行时配置;覆盖只作用于系统任务与定时任务操作者。
  • 是否写入审计 MUST NOT 改变业务操作的结果或接口响应。

Scenario: 人工操作始终写审计

  • GIVEN 动作 order.create 对系统任务声明为只写应用日志
  • WHEN 后台账号创建订单
  • THEN 系统写入审计事件与资源快照

Scenario: 新增机器动作未声明策略

  • WHEN 系统任务写一个未声明落库策略的动作
  • THEN 审计事件表不新增记录,应用日志新增一行该动作的结构化日志

Scenario: 覆盖扫描发现未声明策略

  • GIVEN 一个可由系统任务发起的动作没有声明落库策略
  • WHEN 运行审计覆盖扫描
  • THEN 扫描失败并列出该动作编码

Scenario: 运行时覆盖

  • GIVEN 运行时配置把 package_usage.expire 覆盖为只写应用日志
  • WHEN 定时任务使一个套餐到期
  • THEN 审计事件表不新增记录,应用日志新增一行
  • AND 删除该覆盖后,下一次到期重新写审计事件

Requirement: 系统过程动作写应用日志

落库策略为"只写应用日志",或"只写成功"时的失败结果,系统 SHALL 在所在服务的应用日志中写一行结构化日志,并 MUST NOT 写入审计事件表与资源表。

  • 日志行 MUST 带固定 logger 名 audit。
  • 日志行 MUST 包含:动作编码、操作者类型与标识、来源、结果、资源类型与资源键、关联标识、请求标识、错误码、摘要。
  • 日志行 MUST NOT 包含变更前后快照、主体数据、密钥、令牌或加密报文。
  • 成功写 Info 级别,失败写 Warn 级别。
  • 应用日志写入失败 MUST NOT 重试,也 MUST NOT 影响业务结果。

Scenario: 网络状态同步

  • WHEN 系统任务同步一张卡的网络状态
  • THEN 应用日志新增一行 logger=audit、动作编码为 iot_card.worker_network_sync 的记录
  • AND 该行不含变更前后快照

Scenario: 不新增审计表数据

  • WHEN 系统任务写任一只写应用日志的动作
  • THEN tb_audit_event 与 tb_audit_event_resource 均无新增

Requirement: 自动重试动作只写结论

落库策略为"只写结论"的动作,系统 SHALL 只在最终结论产生时写一条审计事件。最终结论指最终成功,或重试用尽转人工处理。

  • 中间失败 MUST NOT 写审计事件,只写应用日志;日志行 MUST 带尝试序号与对应尝试记录标识。
  • 结论事件的元数据 MUST 包含总尝试次数、首次失败时间与最后错误码。
  • 同一业务对象的同一结论 MUST 只有一条审计事件。
  • 每一次尝试的细节 MUST 由对应业务尝试记录保存,并可分页查询。

Scenario: 重试后成功

  • GIVEN 一张退款单的资产后处理前两次失败、第三次成功
  • WHEN 第三次处理完成
  • THEN 审计事件表只有一条结论为成功的事件,元数据中总尝试次数为 3
  • AND 尝试记录中有三条记录

Scenario: 重试用尽转人工

  • GIVEN 一张退款单的资产后处理连续失败达到上限
  • WHEN 系统将其标记为需人工处理
  • THEN 审计事件表写入一条结论为失败的事件,包含最后错误码

Scenario: 结论重复写入

  • WHEN 同一退款单的成功结论被重复提交
  • THEN 审计事件表仍只有一条该结论事件