固化七月迭代审计治理进展以隔离线上热修

Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。

Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。

Confidence: medium

Scope-risk: broad

Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。

Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。

Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
This commit is contained in:
2026-08-03 09:47:22 +08:00
parent cf2ff0ac1c
commit b3499adfca
114 changed files with 16961 additions and 2782 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
# 主体资源活动接口
代理和企业使用独立安全投影,不得调用 `/api/admin/audit/*`,也不复用平台审计 DTO。
## 接口
- 代理:`GET /api/admin/agent/resource-activities/{resource_type}/{identifier}`
- 企业:`GET /api/admin/enterprise/resource-activities/{resource_type}/{identifier}`
- 查询参数:`page` 默认 1`page_size` 默认 20、最大 100。
响应资源摘要仅包含 `resource_type/resource_id/resource_key/display_name`;活动项仅包含动作编码及中文名、`subject_summary`、Registry 白名单约束的 `subject_data`、结果、发生时间和当前主体仍有权查看的关联资源摘要。
平台操作者、内部原因和备注、风险、内部 `before/after`、Audit Event ID、Integration Log 内容及系统安全凭据均不返回。`internal_only` 事件不会产生占位记录或数量提示。
## 页面字段映射
| 主体与前置接口 | `response.data` 字段 | 目标调用 | 降级行为 |
|---|---|---|---|
| 代理卡资产详情 `GET /api/admin/assets/resolve/{identifier}` | `asset_type=card``iccid` | `/agent/resource-activities/iot_card/{iccid}` | ICCID 为空时隐藏入口 |
| 代理设备资产详情 | `asset_type=device``virtual_no` | `/agent/resource-activities/device/{virtual_no}` | VirtualNo 为空时隐藏入口 |
| 代理分配记录 | `allocation_no` | `/agent/resource-activities/asset_allocation_record/{allocation_no}` | 后端独立复核记录关联店铺和当前资产归属 |
| 代理换货单 | `exchange_no` | `/agent/resource-activities/exchange_order/{exchange_no}` | 后端按换货单当前所属店铺复核 |
| 代理店铺 | `shop_code` | `/agent/resource-activities/shop/{shop_code}` | 仅自己及下级店铺 |
| 代理归属企业 | `enterprise_code` | `/agent/resource-activities/enterprise/{enterprise_code}` | 仅 owner shop 在代理范围内 |
| 企业卡列表 `GET /api/admin/enterprises/{id}/cards` | `items[].iccid` | `/enterprise/resource-activities/iot_card/{iccid}` | 路由企业 ID 不作为授权证明 |
| 企业设备列表 `GET /api/admin/enterprises/{id}/devices` | `items[].virtual_no` | `/enterprise/resource-activities/device/{virtual_no}` | 授权撤销或字段为空时隐藏入口 |
企业不能调用统一资产 resolve 接口,因此不从该页面构造活动入口。所有 shop ID、enterprise ID 和授权范围都来自认证上下文;调用方不能通过 query、path 或 body 伪造。资源不存在、不支持、越权或授权已撤销统一返回“无权限操作该资源或资源不存在”。
`GET /api/admin/assets/{identifier}/operation-logs` 仅保留为平台切换前历史入口,不向代理或企业开放,也不把旧记录拼接到新活动接口。

View File

@@ -0,0 +1,26 @@
# 外部集成调查接口
本文冻结 `build-multi-view-audit-center` 任务 3.4 的只读接口与跳转契约。
## 接口
| 视角 | 接口 | 参数来源 |
|---|---|---|
| 总览 | `GET /api/admin/audit/integrations/overview` | 调查筛选区或关联视角携带的稳定筛选值;必须提供 RFC3339 时间范围 |
| 列表 | `GET /api/admin/audit/integrations` | 调查筛选区、通知目标或关联调查节点;默认每页 20最大 100 |
| 详情 | `GET /api/admin/audit/integrations/{integration_id}` | 列表返回的 `integration_id`、通知目标 `target_key` 或调查节点稳定引用 |
认证身份只来自认证上下文。接口仅允许超级管理员和平台账号访问,全部为 GET不提供重试、补偿、结果确认、外部单号绑定、人工恢复、修改、删除或导出能力。
## 通知跳转
点击通知时先调用 `GET /api/admin/notifications/{id}/target`。仅当响应同时满足 `available=true``target_type=integration_log``target_key` 非空时,前端才将 `target_key` 原样作为 `integration_id` 打开详情;目标不可用时只展示通知正文,不解析通知列表的 `ref_type/ref_id/ref_key` 猜测目标。
## 调查与降级规则
- provider、direction、operation、result 等筛选使用后端稳定编码,时间范围最长 31 天。
- 详情使用稳定 `integration_id`,不接受数据库自增 ID。
- attempts 只按相同非空 `trigger_series` 组织;`correlation_id` 只表示业务链路,不代表技术重试。
- 缺少稳定 ID 时隐藏跳转,不按相似资源、相近时间、中文描述或编号前缀猜测。
- 请求摘要、响应摘要和 metadata 返回前再次删除安全凭据;第三方原始错误正文不直接展示。
- 第一阶段不增加 JSONB GIN 或任意全文搜索。

View File

@@ -0,0 +1,59 @@
# 平台基础审计调查接口
本文冻结 `build-multi-view-audit-center` 任务 2.4 的接口、业务页面参数来源和降级规则。字段路径均位于统一响应的 `response.data` 下;列表字段位于 `items[]`
## 基础接口
| 视角 | 接口 | 参数来源 |
|---|---|---|
| 全局事件 | `GET /api/admin/audit/events` | 调查筛选区或其他节点携带的稳定筛选值 |
| 事件详情 | `GET /api/admin/audit/events/{event_id}` | `investigation_refs.event_id` 或人工粘贴的稳定 ID |
| 操作者 | `GET /api/admin/audit/actors/{kind}/{id}/events` | `investigation_refs.actor_ref` 或平台账号选择器 |
| 资源搜索 | `GET /api/admin/audit/resources/search` | 调查人员选择类型并输入精确业务标识 |
| 资源时间线 | `GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline` | 业务响应稳定 ID、资源搜索结果或 `investigation_refs.resource_refs[]` |
认证身份和数据范围只来自认证上下文,不接受 query/path/body 伪造。接口仅允许超级管理员和平台账号访问,全部为 GET不提供导出、修改、删除、恢复或处置能力。
## 资产与组织页面映射
| 前置接口 | 稳定字段 | 平台目标 |
|---|---|---|
| 卡列表 `/iot-cards/standalone` | `id` | `iot_card/{id}` |
| 设备列表 `/devices` | `id` | `device/{id}` |
| 设备卡槽 `/devices/{virtual_no}/cards` | `bindings[].iot_card_id``bindings[].id` | `iot_card/{iot_card_id}``device_sim_binding/{id}` |
| 统一资产 `/assets/resolve/{identifier}` | `asset_type``asset_id`、绑定资产 ID | `card` 转换为 `iot_card/{asset_id}``device` 使用 `device/{asset_id}` |
| 分配列表/详情 `/asset-allocation-records[/{id}]` | `id``asset_type``asset_id``related_device_id``related_card_ids[]` | `asset_allocation_record/{id}` 及相应资产资源 ID接口已返回的 `iot_card/device` 不再转换 |
| 换货列表/详情 `/exchanges[/{id}]` | `id`、旧/新 `asset_type``asset_id` | `exchange_order/{id}` 及旧、新资产资源 ID |
| 店铺列表/详情 `/shops[/{id}]` | `id` | `shop/{id}` |
| 企业列表 `/enterprises` | `id` | `enterprise/{id}`;不得假设存在企业详情接口 |
| 企业卡列表 `/enterprises/{id}/cards` | `items[].id` | `iot_card/{id}` |
| 企业设备列表 `/enterprises/{id}/devices` | `items[].device_id` | `device/{device_id}` |
## 交易与资金页面映射
| 前置接口 | 稳定字段 | 平台目标 |
|---|---|---|
| 账号列表/详情 `/accounts[/{id}]` | `id` | `account/{id}` |
| 订单列表/详情 `/orders[/{id}]` | `id` | `order/{id}`;资金视角使用 `order_id={id}` |
| 退款列表/详情 `/refunds[/{id}]` | `id``approval_instance_id` | `refund/{id}`;资金视角使用 `refund_id={id}`;审批非空时使用 `approval_instance/{id}` |
| 代理充值列表/详情 `/agent-recharges[/{id}]` | `id` | `agent_recharge/{id}`;资金视角使用 `recharge_id={id}` |
| 代理在线充值创建结果 | `recharge_id``payment_no` | 资金视角以 `recharge_id` 为第一跳,`payment_no` 仅作额外精确筛选 |
| 资产钱包 `/assets/{identifier}/wallet` | `wallet_id``resource_type``resource_id` | 资金视角使用 `wallet_id`;资产审计使用 `{resource_type}/{resource_id}` |
| 店铺资金概况 `/shops/fund-summary` | `items[].shop_id` | 资金视角使用 `shop_id` |
| 店铺主钱包流水 | path `shop_id`、资产类型与 ID | 资金视角使用 `shop_id`;资产 ID 存在时使用对应资源时间线 |
| 资产钱包流水 | 上层 `wallet_id` | 资金视角使用 `wallet_id`;不解析业务编号前缀猜测资源 |
资金视角将在后续任务交付;当前文档只冻结其第一跳参数,缺失的支付、退款、钱包等关联由服务端 Query 解析。
## 统一调查引用
平台调查节点统一返回 `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}`。前端只使用存在的稳定引用,不解析中文名称、备注、编号前缀或相近时间推断关系。
## 降级规则
- 缺少目标接口必需的稳定 ID 或 identifier 时隐藏入口。
- 只有 Registry Key、没有内部 ID 时先精确资源搜索;零命中或多命中均不自动选择。
- 已删除资源只要节点保留 `resource_type/resource_id`,仍可查看事件快照时间线。
- 代理或企业不得回退调用平台审计、资源搜索或旧 operation log。
- 旧 operation log 保持独立历史入口,不拼接到新审计接口。
- 通知先调用 `/notifications/{id}/target`;仅 `available=true` 时按白名单目标继续跳转。

View File

@@ -39,7 +39,7 @@ debt_amount = max(-balance, 0)
## 既有店铺实际额度调整
`PUT /api/admin/shops/{id}/credit-limit` 用于调整既有店铺主钱包的实际信用额度。按当前产品决定,后端不校验 `shop:credit-limit:manage` 或账号类型;该权限编码只供前端决定是否展示按钮,能够看到按钮的账号即可调用。请求携带钱包 `version`,更新同时约束主钱包类型、版本和调整后的总可用金额,成功后版本加一;降额或关闭信用无法覆盖当前欠款/冻结占用时保持原值。该动作不修改余额、冻结金额,也不创建金额为零的钱包流水。后端授权收紧留待未来单独实施。
`PUT /api/admin/shops/{id}/credit-limit` 用于调整既有店铺主钱包的实际信用额度。按当前产品决定,后端不校验 `shop:credit-limit:manage` 或账号类型;该权限编码只供前端决定是否展示按钮,能够看到按钮的账号即可调用。请求携带 `credit_enabled``credit_limit`;服务端读取钱包当前版本并执行乐观锁条件更新,前端不管理乐观锁。降额或关闭信用无法覆盖当前欠款/冻结占用时保持原值。该动作不修改余额、冻结金额,也不创建金额为零的钱包流水。后端授权收紧留待未来单独实施。
## 统一订单扣款