Files
Neo-ZQYY/docs/_overview/wave1-findings/00-W1-findings-stories.md
Neo 658aa7e12b docs(audit): Wave 1 发现待 Neo 拍板 12 项业务故事卡
Wave 1 Day 1-4 实施过程中挖出的问题, 按 04a/b/c 业务故事卡风格呈现:
- 7 字段 (关联/背景/逻辑/影响/选项/判定)
- 12 项分 3 组: P0 评估发现×5 / 项目治理×2 / 业务语义×5
- 每条带 Wave 分配建议, 不评估工时

第一组 P0 评估发现 (W1-T7 PRD 撰写挖出):
- F1-1 批量 AI 长事务无幂等 (重复扣费风险)
- F1-2 run-logs PII 跨租户泄露 (个保法风险, 建议 Wave 1 修)
- F1-3 batch_id 生命周期未管理 (数据库膨胀)
- F1-4 triggers/unified 权限过松 (跨租户可见)
- F1-5 沙箱 batch-run 未读 runtime_context (沙箱主线必修)

第二组 项目治理:
- F2-1 OpenAPI 与代码不同步 (建议 Wave 2 提前修脚本)
- F2-2 tests/ .gitignore 排除 (建议 Wave 5 入仓 + 启 CI)

第三组 业务语义待 Neo 答:
- F3-1 cache invalidate 粒度
- F3-2 AI budget 单价来源
- F3-3 手动触发 audit + 二次确认
- F3-4 sandbox_date 边界
- F3-5 unified 分页
2026-05-04 22:37:04 +08:00

446 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Wave 1 发现待 Neo 拍板 — 业务故事卡
> 日期:2026-05-04 / 来源:Wave 1 Day 1-4 实施过程中挖出的问题
> 用途:对齐 04a/b/c-feedback 风格,以业务上下文呈现,让 Neo 可读可判可拍
> 12 项总览 → P0 评估发现×5 / 项目治理×2 / 业务语义×5
> 注:本轮**不评估工时**,Neo 拍判定后主线自动安排到合适 Wave
---
## 第一组 P0 评估发现(5 项)
### F1-1. 批量 AI 任务 长事务无幂等(可能重复扣费)
**关联页面/接口**:
- admin-web 路由:`/ai/operations`(AI 手动操作页)
- 后端端点:`POST /api/admin/ai/batch-run` + `POST /api/admin/ai/batch-run/confirm`
- 后端代码:`apps/backend/app/routers/admin_ai.py`(批量任务创建/确认)
- 数据库:`biz.ai_run_logs``biz.ai_run_costs`(token 计费)
- 影响 AI 应用:全部 8 个 APP(因为批量入口通用)
**业务背景**:
admin-web `/ai/operations` 提供"批量触发"按钮,运维一次点击可以为多个 site 或多组合发起 AI 任务(典型场景:8 区域财务洞察并发跑)。后端做法:先 `POST /batch-run` 创建批次返回 batch_id,再 `POST /batch-run/confirm` 触发实际 dashscope 调用。
**问题描述**:
- 整个流程**一个事务包了"创建批次 + 调 N 个 AI"**
- 如果调用 dashscope 第 5 个时进程崩溃,前 4 次的 token 已经扣了(dashscope 计费),但事务回滚 → `ai_run_costs` 没记录这 4 次费用
- 运维不知道实际花了多少 → **预算追踪失真**
- 重启后又重跑一遍,可能再扣 4 次费用 → **重复扣费**
**业务联系**:
- 上游:运维点"批量触发"按钮
- 下游:AI cache 写入 / `ai_run_costs` 计费 / 触发器统计
- 影响范围:每次批量 AI 任务都有这个风险,门店数 × 应用数越多风险越大
**修改影响**:
- 数据层:`ai_run_costs` 写入策略改为"单次调用即写,不等批次完成"
- 接口层:`/batch-run/confirm` 改为异步队列消费 + 单次幂等
- 展示层:admin-web `/ai/run-logs` 可能需要"部分完成"状态
**推荐选项**:
1. **A 拆事务 + 幂等 token**:每次 AI 调用前生成 idempotency_key,失败重启可幂等跳过 → 优:正确 劣:改造面广
2. **B 同步 → 异步队列**:`/batch-run` 入队,worker 单次消费,数据库做唯一约束防重 → 优:架构清晰 劣:引入队列基建
3. **C 仅打 audit log,不改架构**:崩溃后人工对账 → 优:最快 劣:依赖人,不可持续
**建议判定**:**[D Bug] 升级到 Wave 2(随 P1-7 admin API PRD 批 2)**,选 A(因为 Wave 2 已经会改 admin-ai 路由)
---
### F1-2. AI 运行日志 PII 跨租户泄露(super_admin 看到其他门店用户名)
**关联页面/接口**:
- admin-web 路由:`/logs/ai-run-logs`
- 后端端点:`GET /api/admin/ai/run-logs``GET /api/admin/ai/run-logs/{log_id}`
- 数据库:`biz.ai_run_logs`(含 user_id、site_id、prompt、response 字段)
- 关联 Wave 0 决策:P0-5 致命 1+2 已修,但本项是新发现的另一处租户隔离 Bug
**业务背景**:
admin-web AI 运行日志页给运维看 AI 调用历史(谁触发的、prompt 是什么、token 用了多少、有没有失败)。其中 prompt 字段会**包含用户姓名 / 客户姓名 / 助教姓名**(因为 prompt 模板把这些注入了)。
**问题描述**:
- 端点未加 `WHERE site_id = current_setting('app.current_site_id')` RLS 过滤
- super_admin 一旦切到任何 site,**能看到所有 site 的日志**(包括 prompt 里的客户姓名 / 手机号尾号 / 助教姓名)
- 多租户上线后:租户 A 的运维查日志可能误看到租户 B 的客户 PII
**业务联系**:
- 上游:AI 调用产生日志写入 `biz.ai_run_logs`
- 下游:admin-web `/logs/ai-run-logs` 列表 / 详情
- 法律影响:多租户场景下涉嫌"未授权访问个人信息",违反《个人信息保护法》第 51 条(责任主体的访问控制义务)
**修改影响**:
- 数据层:`biz.ai_run_logs` 加 RLS 策略(已有 site_id 列)
- 接口层:`run-logs` 端点加 `SET LOCAL app.current_site_id`
- 展示层:super_admin 跨 site 时显式切站才能看到对应日志(符合最小授权)
- 兜底:勿动 prompt 字段(脱敏成本高)
**业务联系**(进一步):
- 与 P0-5 致命 1+2 同类(都是租户隔离),Wave 1 Day 1 已修 4 处 fdw_etl,这是第 5 处遗漏
- 与 P0-7 沙箱无直接冲突(沙箱影响时间不影响 site 隔离)
**推荐选项**:
1. **A 加 RLS + 强制选 site**:类似 admin-runtime-context 行为,super_admin 必须先选 site → 优:最干净 劣:UX 多一步
2. **B 后端 query 强制带 site_id 参数**:不依赖 GUC,显式入参 → 优:简单 劣:跟全局一致性脱钩
3. **C 仅记 audit log,不限制访问**:运维知情同意 → 优:零改 劣:法律风险高
**建议判定**:**[D Bug 安全] 升级到 Wave 1(本 Wave 内修)**,选 A(与 admin-runtime-context UX 一致)
---
### F1-3. AI 批次 batch_id 生命周期未管理(数据库永久膨胀)
**关联页面/接口**:
- admin-web 路由:`/ai/operations` + `/logs/ai-run-logs`
- 后端端点:`POST /api/admin/ai/batch-run` + `POST /confirm`
- 数据库:`biz.ai_run_logs`(每次批次产生 N 行)、`biz.ai_run_costs`、可能还有 `biz.ai_batches`(批次主表)
**业务背景**:
每次批量 AI 任务产生一个 batch_id,关联一组 ai_run_logs。一年下来如果每周跑一次 8 区域财务洞察预热,会产生 52 × 8 = 416 行 ai_run_logs(还没算其他 APP)。生产环境跑 1-2 年后,这张表会膨胀到几十万行。
**问题描述**:
- `biz.ai_run_logs` **没有 retention / archive 策略**
- 也没有 `cleanup_ai_run_logs_after_days` 类似配置
- admin-web 的 run-logs 列表查询可能从"快"变"慢"
- 数据库备份大小持续上涨
**业务联系**:
- 上游:每个 AI 调用都写 1 行
- 下游:列表查询 / 计费报表 / 异常排查
- 与沙箱关联:沙箱模式下也会写 run_logs 但带 `runtime_mode='sandbox'`,如果不区分清理会丢沙箱回放数据
**修改影响**:
- 数据层:加 `cleanup_ai_run_logs.py` 定期任务(保留 N 天 / 归档前 M 天到对象存储)
- 接口层:可选加 `GET /api/admin/ai/run-logs/archive` 查归档
- 展示层:admin-web 列表注明"最近 N 天",超出范围跳归档查询
**推荐选项**:
1. **A 简单 retention**:cron 每日删 90 天前的 live 日志,sandbox 日志保留 7 天 → 优:简单 劣:历史不可查
2. **B retention + 冷归档**:90 天后归档到对象存储(parquet / 或 cold table),admin-web 提供"查归档"入口 → 优:历史可查 劣:基建复杂
3. **C 仅打监控告警**:数据库行数 > 阈值时报警,不自动清理 → 优:零改 劣:被动响应
**建议判定**:**[P1] 留 Wave 4**(DWS / 数据正确性 Wave),选 A 起步,B 后续观察
---
### F1-4. triggers/unified 权限过松(任意 admin 看全局)
**关联页面/接口**:
- admin-web 路由:`/triggers`(触发器统一管理页)
- 后端端点:`GET /api/admin/triggers/unified`(P1-6 调研发现的第 3 个 API)
- 数据库:跨表只读聚合(`biz.trigger_jobs` + ai_trigger_jobs + scheduled_tasks)
**业务背景**:
admin-web `/triggers` 顶部按 Tab 展示业务触发器 / AI 触发器,底部有个"统一视图"显示跨类型聚合。这个聚合数据来自 `/admin/triggers/unified` 端点。
**问题描述**:
- 该端点只校验 `admin` 角色,**不校验 super_admin**
- 任何 admin(含 site_admin)登录后能看**所有 site 的触发器**(因为 unified 是聚合视图,不按 site 过滤)
- 多租户后:租户 A 的 site_admin 能看到租户 B 的触发器名称 / cron / status
**业务联系**:
- 上游:运维查"全局调度状态"
- 下游:仅展示,不直接动数据
- 与 P1-6 关联:P1-6 已规划"扩展 /trigger-jobs PATCH + 业务触发器禁用守卫 + 保留 unified",**本项是 unified 的额外权限收紧**(原 P1-6 没覆盖)
**修改影响**:
- 接口层:`/admin/triggers/unified``super_admin` 守卫,或加 site_id 过滤
- 展示层:site_admin 看不到"统一视图" Tab(仅 super_admin 可见)
**推荐选项**:
1. **A super_admin 才可见**:unified 是跨 site 的全局视图,符合 super_admin 职责 → 优:语义清晰 劣:site_admin 失去全局视图
2. **B 按当前 site_id 过滤**:站内 unified 视图 → 优:site_admin 仍可用 劣:与 unified 跨表语义冲突
3. **C 维持现状,文档警告**:零改 劣:租户隔离失效
**建议判定**:**[D Bug 安全] 升级到 Wave 2**(随 P1-6 合并修),选 A
---
### F1-5. 沙箱模式下批量 AI 任务未校验 runtime_context(切沙箱后仍按真实时钟跑)
**关联页面/接口**:
- admin-web 路由:`/ai/operations`(批量任务入口)
- 后端端点:`POST /api/admin/ai/batch-run``POST /api/admin/ai/run/{app_type}`
- runtime context:`biz.site_runtime_context`(P20 SPEC 主表)
- 影响 AI 应用:全部 8 APP
**业务背景**:
P20 沙箱设计:切 sandbox 后,所有"读取业务数据"的查询应该按 sandbox_date 切片(看到历史日期当时的数据)。批量 AI 任务的 prompt 模板会嵌入"current_time" / "ref_date" 等字段,沙箱模式下这些应该是 sandbox_date,而不是真实今天。
**问题描述**:
- `/api/admin/ai/batch-run` 创建批次时**没读 site_runtime_context**
- prompt 模板里的"current_time"被设成 `datetime.now()`(真实今天)
- 切沙箱回放 2026-03-01 时,AI 看到的"今天"还是真实日期(如 2026-05-04)
- AI 输出的洞察文案会说"截至今天 5 月 4 日,本月营收..." → **跟沙箱数据(3 月)语义错位**
**业务联系**:
- 上游:运维切 sandbox + 触发批量预热
- 下游:AI 输出文案语义错位 → 沙箱演示效果失真
- 与 P0-3 看板沙箱关联:P0-3 已修小程序看板,但 AI 入口还没接(本项)
- 与 P20 SPEC §10.2 关联:AI 提示词矩阵标了 X 已接入,但**这是 prompt builder 接入,不是 admin-web 触发入口接入**
**修改影响**:
- 接口层:`batch-run` / `run/{app_type}` 在创建任务前 `get_runtime_context(site_id)`,把 sandbox_date 传给 prompt builder
- 数据层:`ai_run_logs.runtime_mode` + `sandbox_instance_id` 列(P20 已规划)
- 展示层:admin-web `/ai/operations` 顶部条带显示当前 sandbox_date(如非 live)
**推荐选项**:
1. **A 完整接入沙箱**:batch-run 读 runtime_context 传给 prompt + 写 ai_run_logs.runtime_mode → 优:沙箱主线达标 劣:Wave 1 工作量略增
2. **B 只 block 沙箱模式**:sandbox 时 batch-run 直接 422 拒绝(不允许沙箱跑批量)→ 优:简单 劣:沙箱演示功能受限
3. **C 留 Wave 2 处理**:Wave 1 不修,P20 §10.2 标"待补" → 优:不阻塞 Wave 1 劣:沙箱主线收口延后
**建议判定**:**[D Bug 沙箱主线] 升级到 Wave 1**(P0-7 沙箱收口主线,本 Wave 必修),选 A
---
## 第二组 项目治理(2 项)
### F2-1. OpenAPI 与代码不同步(本批 23 端点中 10 个不在 backend-api.json)
**关联页面/接口**:
- 项目级:`docs/contracts/openapi/backend-api.json`(OpenAPI 静态导出)
- 抓取脚本:`tools/codex/...` 或类似(具体位置 Wave 1 没查到)
- 影响:全部 admin-web API PRD 工作流
**业务背景**:
admin-web API PRD(P1-7)的工作流是"自动生成 + 人工细化",依赖 backend-api.json 作为权威源。批 1 撰写时发现:**本批 23 端点中 10 个不在 OpenAPI 文件**,意味着每批 PRD 都可能漏 30%+ 端点。
**问题描述**:
- `backend-api.json` 缺以下端点:
- 全部 `/api/admin/runtime-context/*`(5 个)
- `/api/admin/triggers/unified`(1 个)
- `admin_ai` 后期扩展 4 个端点(`/run/{app_type}``/triggers` GET&PATCH、`/prewarm/progress``/trigger-event`)
- 推测原因:OpenAPI 静态导出脚本未含某些后注册的 router,或抓取时服务未启动全部 router
- backend-api.json 上次更新时间:Wave 0 迁移期(2026-04-XX)?
**业务联系**:
- 上游:后端 router 改动 → 应触发 OpenAPI 重抓
- 下游:admin-web 前端类型生成 / API PRD / 文档同步
- Wave 关联:批 2-5 会持续依赖 OpenAPI,如果不修,Wave 2-4 可能各漏 10-20%
**修改影响**:
- 工程层:修抓取脚本(确保启动包含所有 router 的服务)
- 文档层:`backend-api.json` 重新生成 + 入仓
- 工作流:加 git pre-commit / CI 钩子检查 OpenAPI 与代码同步(可选)
**推荐选项**:
1. **A Wave 2 提前修**(我推荐):批 2 撰写前先修脚本,避免后续批漏端点 → 优:止血最早 劣:Wave 2 多一项准备工作
2. **B Wave 5 文档收尾时统一修**:5 批合并时一次校核 + 修补 → 优:跟收尾节奏一致 劣:批 2-5 各漏端点时需手工补
3. **C 不修脚本,人工对照代码补 PRD**:每批主线手动 grep router 补漏 → 优:零基建 劣:容易漏 + 持续工作量
**建议判定**:**[C 待补] Wave 2 提前修**,选 A
---
### F2-2. tests/ 不入仓(.gitignore:71 排除测试代码)
**关联页面/接口**:
- 项目级:`.gitignore:71` `tests/`
- 影响范围:**全部子项目**(backend / etl / miniprogram / admin-web / tenant-admin)
- Wave 1 实例:W1-T4 audience 7 单测 + W1-T5 db_viewer 测试改动留本地
**业务背景**:
Wave 1 Day 4 跑测试时发现:`apps/backend/tests/` 全部测试文件**从未被 tracked**(`git ls-files apps/backend/tests/test_auth_jwt.py` 返回空)。原因:`.gitignore:71` 写了 `tests/` 排除全部测试目录。
**问题描述**:
- 测试代码不入仓 → CI 没法跑测试 → 测试可能 rot
- 各子项目测试只在本地 → 不同开发者机器测试覆盖不一
- Wave 1 我加的 jwt audience 7 单测(75% 覆盖 W1-T4)留本地,无法保护后续 Wave 不破坏 audience 行为
- 这是项目级早期决策(可能是为了避开 venv / pytest_cache 误入仓)
**业务联系**:
- 上游:开发写测试(本地有效)
- 下游:CI / 后续 Wave / 跨设备协作(都受影响)
- 与 P1-13 tasks.md 撒谎对照:tasks.md 撒谎是"声明做了实际没做",tests 不入仓是"做了别人看不到" — 两者都侵蚀测试可信度
**修改影响**:
- `.gitignore:71` 改为更精细的排除(如 `**/__pycache__/` `**/.pytest_cache/` `**/*.egg-info/`)
- 全部子项目测试目录入仓
- CI 配置(可选,Wave 5 决议)
- 注意:某些 tests 内可能含 fixtures / 测试库 dump,需要先脱敏
**推荐选项**:
1. **A 改 .gitignore + 入仓 + Wave 5 启 CI**(我推荐):彻底解决,CI 反馈 → 优:长期投资 劣:需要先扫一遍现有 tests/ 是否含敏感数据
2. **B 改 .gitignore + 入仓 + 不启 CI**:先解决"代码可见",CI 后续再说 → 优:渐进 劣:仍依赖人工跑
3. **C 维持现状**:接受 tests rot 风险 → 优:零改 劣:Wave 5 收尾时无法证明测试通过
**建议判定**:**[B 现状对 待优化] Wave 5 处理**,选 A(需先脱敏扫描)
---
## 第三组 批 1 PRD 业务语义待答(5 项)
### F3-1. AI cache 失效粒度
**关联**:`POST /api/admin/ai/cache/invalidate``biz.ai_cache`
**业务背景**:
admin-web 提供"清空 AI 缓存"按钮,运维在 prompt 调优后清缓存让下次调用拿新版本。当前实现按 cache_key 整张表清,**可能误伤所有 site 的缓存**。
**冲突逻辑**:
- 设计意图:某 site 的 prompt 改了 → 只清该 site 的缓存
- 实际实现:`DELETE FROM biz.ai_cache WHERE cache_key LIKE ?`,不带 site_id 过滤
**业务联系**:
- 上游:运维改 prompt 后清缓存
- 下游:其他 site 缓存被误清 → 下次访问触发 AI 调用 → token 浪费
**修改影响**:
- 接口层:加 `site_id` 参数(可选,不传则当前 site)
- 数据层:无 schema 变更
- 展示层:admin-web 按钮加 site 选择器
**推荐选项**:
1. **A 按当前 site 清**:默认清当前 super_admin 选的 site → 优:安全 劣:运维需先切 site
2. **B 显式 ?scope=site|tenant|all**:运维选粒度,默认 site → 优:灵活 劣:UI 多按钮
3. **C 维持现状(全表清)**:简单粗暴 → 优:简单 劣:误伤
**建议判定**:**待 Neo 拍板**,我倾向 **B**(灵活 + 默认安全)
---
### F3-2. AI budget 单价是估算还是实际计费?
**关联**:`GET /api/admin/ai/budget`、DashScope 计费
**业务背景**:
admin-web 显示"本月 AI 预算 X 元 / 已用 Y 元"。Y 来自 `biz.ai_run_costs` 累计 token × 单价。但单价配置存哪里?DashScope 实际单价(qwen-max / qwen-turbo)是浮动的还是固定的?
**冲突逻辑**:
- 配置层:`config.AI_TOKEN_PRICE_*`(代码里硬编码?cfg 表?)
- 计费层:DashScope API 返回 token usage,后端按单价转元
- 实际值:DashScope 控制台账单(权威)
**业务联系**:
- 上游:每次 AI 调用记 token
- 下游:budget 展示 / 上线前预算估算 / 超额告警
- 上线门槛:P11 上线时 budget 准确性是验收点之一
**修改影响**:
- 配置层:加 `cfg_ai_token_price`(SCD2 表,按 model + 时间切片)
- 接口层:budget 端点改读 cfg 表
- 对账:每月与 DashScope 账单对账,误差 > 5% 报警
**推荐选项**:
1. **A 配置表 + 月度对账**:正确性最高 → 优:可审计 劣:对账自动化成本
2. **B 硬编码单价**:维持简单 → 优:0 改 劣:DashScope 调价后失准
3. **C 用 DashScope SDK 返回的实际计费值**:如果 SDK 返回 cost_yuan 字段,直接用 → 优:最准 劣:依赖 SDK
**建议判定**:**待 Neo 答** — 你知道 DashScope SDK 是返 token 还是 cost,这影响选项
---
### F3-3. 手动触发 AI 调用是否记 audit_log + 二次确认?
**关联**:`POST /api/admin/ai/run/{app_type}`、admin-web `/ai/operations`
**业务背景**:
admin-web 提供"手动触发某 APP"按钮,运维点了直接调 dashscope。区别于 P0-6 clearAllTasks(那是清数据),这个是"花钱 + 不可撤销 + 可能产生 PII 写入"。
**冲突逻辑**:
- 当前:点击直接调用,不记 audit
- 期望:符合"可追溯 + 可控制成本"原则,应该记日志 + 弹窗确认
**业务联系**:
- 上游:运维或测试人员手动触发
- 下游:dashscope 扣费 + 日志写入 + 可能影响小程序展示
- 与 P0-6 对照:P0-6 是清空(数据风险),F3-3 是调用(费用 + PII)
**修改影响**:
- 接口层:`/run/{app_type}` 加 audit_log 写入(`biz.admin_audit_log`,含 user / params / timestamp)
- 展示层:admin-web 加二次确认弹窗(可选关闭)
**推荐选项**:
1. **A 加 audit_log + 二次确认**(我推荐):标准做法 → 优:可追溯 + 防误操作 劣:UX 多一步
2. **B 仅加 audit_log,不加二次确认**:运维信任度高 → 优:UX 流畅 劣:误点风险
3. **C 维持现状**:零改 → 优:简单 劣:无追溯
**建议判定**:**待 Neo 答**,我倾向 A
---
### F3-4. runtime-context 切 sandbox 的"未来日期"边界
**关联**:`PATCH /api/admin/runtime-context`、admin-web `/settings/runtime-context`
**业务背景**:
admin-web 沙箱页可以切 sandbox_date。当前代码校验:
```python
if sandbox_date > date.today():
raise HTTPException(422, "沙箱日期不能晚于今天")
```
**冲突逻辑**:
- 设计:沙箱是"回放历史",上限 = today
- 但 P20 SPEC §3.1 没明确写"sandbox_date 是否可以等于 today"
- 边界:`> today` 拒绝,`= today` 允许(当前实现)
- 问题:`= today` 时沙箱跟 live 几乎一致,有意义吗?
**业务联系**:
- 上游:运维切 sandbox 用于演示 / 调试
- 下游:全部读 sandbox_date 的逻辑
**修改影响**:
- P20 SPEC §3.1 文字补充
- 后端校验逻辑(若需收紧)
- admin-web 日期选择器 maxDate 配置
**推荐选项**:
1. **A 维持现状(`> today` 拒绝,`= today` 允许)**:今天也算合法回放(数据齐全)→ 优:零改 劣:沙箱意义弱
2. **B 严格 `< today`**:沙箱必须是历史 → 优:语义清晰 劣:今天演示需切 live
3. **C 提示 + 允许**:`= today` 时 admin-web 提示"今天数据可能不完整(实时更新中)" → 优:体验好 劣:UI 多一项
**建议判定**:**待 Neo 答**(产品决策),我倾向 A 或 C
---
### F3-5. triggers/unified 是否需要分页
**关联**:`GET /api/admin/triggers/unified`、admin-web `/triggers`
**业务背景**:
unified 端点跨表聚合返回所有触发器(含 biz / ai 两类)。当前**全表返回,不分页**。
**冲突逻辑**:
- 当前实现:全量返回(预计 < 100 条)
- 真实场景:门店多 + AI 触发器多 → 100-500 条
- 极端场景:多租户 + 50 门店 × 10 触发器 = 500 条
- 单 row JSON 估 200 bytes → 100KB(可接受)~ 1MB(慢)~ 10MB(慢且占带宽)
**业务联系**:
- 上游:运维浏览触发器
- 下游:列表渲染 / 筛选
**修改影响**:
- 接口层:加 `?page=&page_size=`
- 展示层:admin-web 列表加分页器
- 跟 P1-6 关联:P1-6 合并方案保留 unified,可顺带加分页
**推荐选项**:
1. **A 加分页(默认 page_size=50)**:标准做法 → 优:任意规模可承受 劣:Wave 2 改造时多一项
2. **B 全量 + 前端分页**:后端简单 → 优:简单 劣:1000+ 触发器时仍卡
3. **C 维持现状**:门店少时无问题 → 优:零改 劣:多租户后必爆
**建议判定**:**待 Neo 答**,我倾向 A,**留 Wave 4**(数据/性能 Wave),不阻塞 Wave 2 P1-6 合并主线
---
## 总览决策清单
| # | 简述 | 我的建议 | Wave |
|---|---|---|---|
| F1-1 | 批量 AI 长事务无幂等 | D Bug 选 A | Wave 2 |
| F1-2 | run-logs PII 跨租户泄露 | D Bug 选 A | **Wave 1** |
| F1-3 | batch_id 数据库膨胀 | P1 选 A | Wave 4 |
| F1-4 | triggers/unified 权限过松 | D Bug 选 A | Wave 2 |
| F1-5 | 沙箱 batch-run 未读 runtime | D Bug 选 A | **Wave 1** |
| F2-1 | OpenAPI 不同步 | C 待补 选 A | Wave 2 提前修 |
| F2-2 | tests/ 不入仓 | B 待优化 选 A | Wave 5 |
| F3-1 | AI cache 失效粒度 | 待 Neo 答 (B 倾向) | Wave 2 |
| F3-2 | AI budget 单价来源 | 待 Neo 答 | Wave 2-4 |
| F3-3 | 手动触发 audit + 确认 | 待 Neo 答 (A 倾向) | Wave 2 |
| F3-4 | sandbox_date 边界 | 待 Neo 答 (A/C 倾向) | Wave 5 文档 |
| F3-5 | unified 分页 | 待 Neo 答 (A 倾向) | Wave 4 |
---
> 等 Neo 在每条卡上写斜体反馈(类似 04a/b/c),主线接斜体即按判定走 Wave。