From 658aa7e12be5d28efeaf36a0d612fa5947a0369d Mon Sep 17 00:00:00 2001 From: Neo Date: Mon, 4 May 2026 22:37:04 +0800 Subject: [PATCH] =?UTF-8?q?docs(audit):=20Wave=201=20=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E5=BE=85=20Neo=20=E6=8B=8D=E6=9D=BF=2012=20=E9=A1=B9=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=95=85=E4=BA=8B=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 分页 --- .../wave1-findings/00-W1-findings-stories.md | 445 ++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 docs/_overview/wave1-findings/00-W1-findings-stories.md diff --git a/docs/_overview/wave1-findings/00-W1-findings-stories.md b/docs/_overview/wave1-findings/00-W1-findings-stories.md new file mode 100644 index 0000000..0b2d183 --- /dev/null +++ b/docs/_overview/wave1-findings/00-W1-findings-stories.md @@ -0,0 +1,445 @@ +# 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。