Files
Neo-ZQYY/docs/_overview/wave1-findings/F1-5-impl-decisions.md
Neo 421e193041 fix(ai): F1-5a 沙箱 batch-run 接入 runtime_context (W1 / 阶段 A 主体)
Neo F1-5 反馈: "让沙箱起到其真正的作用. 真正的模拟日期, 仅能看到沙箱设定日期
及之前日期的数据, 并运行 AI 的各个业务."

调研发现 (4 个并行子代理): batch-run 端点 _run_batch 是空壳 stub
(只 logger.info, 实际不跑 AI), GUC apply_runtime_session_vars 0 处调用
(dead code), 7 张业务表 6 张有 runtime 复合索引唯独 ai_run_logs 漏建,
App2/2a 3 行 _calc_date_range 漏传 ref_date.

本 commit (F1-5a 阶段 A 主体, F1-5b 后续完整 zqyy_app RLS 视图层):

后端核心:
- admin_service.py: _run_batch 真实化 (Semaphore(5)+asyncio.gather+
  return_exceptions=True+ctx_snapshot 防漂移); estimate 入口抓
  RuntimeContext 快照, confirm 取出传给 worker
- admin_ai.py: confirm_batch_run lazy 注入 dispatcher
- admin_service.retry_trigger_job: INSERT 落 runtime_mode +
  sandbox_instance_id 列 (用 runtime_insert_columns helper)
- runtime_context.py: get_runtime_context 加 bind_to_session 参数,
  激活 GUC app.current_business_date / app.current_runtime_mode
- run_log_service.create_log: 启用 bind_to_session=True 试点

App2/2a 3 行 ref_date 修复:
- app2_finance_prompt.py:817 储值卡余额变化板块
- app2_finance_prompt.py:841 日粒度 series + 异常检测窗口
- app2a_finance_area_prompt.py:466 区域日粒度 series

DB:
- migrations/20260505__ai_run_logs_runtime_index.sql:
  补 (site_id, runtime_mode, sandbox_instance_id, created_at DESC) 复合索引

前端:
- AIOperations.tsx: 顶部加 sandbox 模式提示条 (Alert 显示 sandbox_date +
  sandbox_instance_id + 影响范围 + 切回 live 入口)

未做 (留 F1-5b 完整 zqyy_app RLS 视图层一并):
- B1 admin_service 6 处 CURRENT_DATE -> business_date
- B2 fdw_queries 异常分支兜底
- GUC 完整传递 (fdw_queries / page_context 等)
- 测试 3 套 (.gitignore:71 排除, F2-2 入仓时 commit)
- P20 SPEC \xa76/\xa710/\xa711/\xa715 (F1-5b 完整收口后同步更准确)

Neo 决策: docs/_overview/wave1-findings/F1-5-impl-decisions.md

详见 docs/audit/changes/2026-05-05__wave1_f1_5a_sandbox_batch_run.md
2026-05-05 03:01:48 +08:00

429 lines
20 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.
# F1-5 沙箱 batch-run 接入 runtime_context — 实施决策卡
> 日期:2026-05-05
> 触发:F1-5 前置调研完成,4 份子代理报告整合后剩 6 项实施决策待 Neo 拍板
> 调研依据:`runtime_context.py:264` / `admin_service.py:622-631` / W1-T7 PRD 批 1(`batch1-runtime-context-and-ai.md:494-547`)/ P20 SPEC §10/§14
> 用法:Neo 在每个决策卡下面用 *斜体* 写反馈/选择,Claude 据此实施
---
## 决策总览
| # | 决策项 | Claude 推荐 | Neo 决定 |
|---|---|---|---|
| D1 | batch executor 实现方式(Semaphore / 全并发 / 串行 / TaskQueue) | Semaphore(3) + asyncio.gather + return_exceptions | (待填) |
| D2 | GUC C 方案处置(全做 / 移除 / 混合) | 混合(纳入阶段 A) | (待填) |
| D3 | 测试覆盖是否纳入 F1-5(同 commit) | 纳入(阶段 A) | (待填) |
| D4 | 走查 mock 数据(切 sandbox=2026-03-01) | 授权我用 PATCH 端点切 + 完成后切回 live | (待填) |
| D5 | commit 拆分(1 个 / 2 个 / 3 个) | 1 个统一 commit | (待填) |
| D6 | P20 SPEC §11/§15/§10 文档同步更新 | 纳入 F1-5 commit | (待填) |
---
## D1. batch executor 实现方式
### 关联页面/接口
- 后端:`apps/backend/app/services/ai/admin_service.py:622-631`(`_run_batch` 当前是空壳 stub)
- 后端:`apps/backend/app/services/ai/admin_service.py:576-620`(`estimate_batch` / `confirm_batch`)
- 后端:`apps/backend/app/routers/admin_ai.py:269-293`(`POST /batch-run` / `POST /batch-run/confirm`)
- 前端:`apps/admin-web/src/api/adminAI.ts:285,290`(`createBatchRun` / `confirmBatchRun`)
- 前端:`apps/admin-web/src/pages/AIOperations.tsx`(批量执行 Modal,两步操作)
### 业务背景
`/admin/ai/batch-run`(两阶段提交)是 admin-web 的批量 AI 调用入口。运维选 N 个 app_type × M 个 member,**估算 token / 调用次数 → 看到预估同意 → confirm 后台异步真跑**。两阶段防误操作打爆 token 预算(因为 1000 个会员一次跑可能几百元)。
**当前代码事实**:
- `estimate` 端点工作 ✓ 算 token / calls
- `confirm` 端点工作 ✓ 返 `{status: started}`,创建 `asyncio.create_task(self._run_batch(...))`
- **`_run_batch` 是占位假动作**:只 `logger.info`,**实际不跑 AI 调用**(注释"实际执行逻辑在路由层通过 dispatcher.handle_trigger 驱动"是 stub 假话)
- 路由层并未注入 dispatcher 到 service 层
### 冲突逻辑
当前 batch-run 端点 = **假动作**:
- admin 看到 estimate 估算 / confirm 返 success
- 后台 logger 只打一行 "_run_batch invoked",**0 个 AI 实际调用**
- ai_run_logs / ai_cache 没有 batch 相关记录
- 这违反 PRD §"业务语义":"投递到 dispatcher"——但实际没投递
### 业务联系
- F1-5(本任务):batch-run 接入 sandbox runtime_context — 但前提是 batch-run **真的能跑**,所以**必须先把 executor 补完**
- W1-T7 PRD 批 1 评估问题已标 P0:"batch_id 生命周期未声明 / 状态查询缺失 / member_ids 无上限"——但**没标"实际不执行"**(文档评估只看 schema 没看深度代码)
- Wave 2 计划:加 `GET /batch-run/{batch_id}/status` 进度查询(本次不做)
- AIPrewarm.tsx(/ai/prewarm)分组展示 72 组合,目前仅"每行触发"用单点 `run/{app_type}` 路径,batch-run 路径补完后可统一
### 修改影响
| 选项 | 影响范围 | 复杂度 |
|---|---|---|
| **a 全并发**(asyncio.gather 无 sem) | 1000 个 member 一次性发 → DashScope 限流熔断 / token 预算瞬间打爆 | 极不安全 ❌ |
| **b 串行**(顺序 await) | 1000 调用 × 2-5 秒 = 30+ 分钟,admin 看不到任何进度,confirm 时刻没回应 | 慢但安全 |
| **c Semaphore 限并发**(我推荐 N=3) | 3 个并发,1000 调用约 10 分钟,与现有 dispatcher 熔断/限流配合 | 平衡 ✅ |
| **d TaskQueue 推送**(走 worker 消费) | 引入跨进程依赖,与 confirm 端点的 `asyncio.create_task` 模型不一致 | 改造大 |
**c 方案细节**:
```python
async def _run_batch(self, params: dict, ctx_snapshot: RuntimeContext) -> None:
sem = asyncio.Semaphore(3)
tasks = []
for app_type in params["app_types"]:
for member_id in params["member_ids"]:
async def _one(at=app_type, mid=member_id):
async with sem:
await self._dispatcher.run_single_app(
app_type=at,
context={
"site_id": params["site_id"],
"member_id": mid,
"business_date": ctx_snapshot.business_date.isoformat(),
},
triggered_by=f"batch:{params['batch_id']}",
)
tasks.append(asyncio.create_task(_one()))
await asyncio.gather(*tasks, return_exceptions=True) # 单失败不连坐
```
- `Semaphore(3)`:限并发 3,与 dispatcher 现有 circuit_breaker / rate_limiter 配合,不打爆 DashScope 1000 RPM 限制
- `triggered_by=f"batch:{batch_id}"`:打标 ai_run_logs,Wave 2 加进度查询 `WHERE triggered_by LIKE 'batch:<id>%'` 即可统计
- `ctx_snapshot`:estimate 阶段抓 RuntimeContext 快照,worker 用快照值,**避免** Neo 在 estimate→confirm 间切 sandbox 模式造成污染
- `return_exceptions=True`:1 个 member 失败不阻断其他 member,失败信息已写 ai_run_logs
### 推荐选项
**c 方案 — Semaphore(3) + asyncio.gather + return_exceptions=True + ctx_snapshot**
### 建议与理由
- 与现有架构(asyncio.create_task / dispatcher 限流)一致,无新引入复杂度
- 限并发 3 是经验值(可调),既快(10 分钟)又安全(不爆限流)
- 快照设计避免最大数据漂移风险(Neo 切模式时 batch 已在跑)
- `triggered_by` 打标为 Wave 2 进度查询埋好钩子
- 单失败不连坐符合"批量任务"行业惯例
**Neo 反馈**:
*同意 c 方案 改并发数 N=5 改进 ctx_snapshot 合适的时机你来定)*
---
## D2. GUC C 方案处置
### 关联页面/接口
- 后端:`apps/backend/app/services/runtime_context.py:244-263`(`apply_runtime_session_vars` 函数,**当前 dead code**)
- DB(ETL):`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql`(26 个 `app.v_*` 视图加业务日上界)
- DB(ETL):`app.business_date_now()` 函数(读 GUC `app.current_business_date`,fallback 真实 today)
- DB(zqyy_app):**无 RLS 视图层**,业务库 SQL 直查 `biz.*`
- SPEC:`docs/prd/specs/P20-runtime-context-sandbox.md` §6(C 方案 GUC 路线)
### 业务背景
P20 沙箱机制要求:Neo 切 sandbox=2026-03-01 后,后端跑 AI / 查询数据时,**只能看到 2026-03-01 及之前数据**(防"未来数据泄露")。
实现方案有两条路线:
- **A 应用层方案**:每个查询函数都加 `business_date` 参数,SQL 里 `WHERE date <= %s` 显式裁剪
- **C GUC 数据库层方案**:DB 连接级 `SET LOCAL app.current_business_date='2026-03-01'`,所有视图自动按 `app.business_date_now()` 裁剪(代码不需要每个 SQL 都改)
### 冲突逻辑
**当前现状是"做了一半的 C 方案 + 大部分 A 方案":**
| 库 | 方案 | 现状 |
|---|---|---|
| etl_feiqiu(ETL) | C 方案 | ✅ 26 个视图加上界 |
| zqyy_app(业务) | A 方案 | ✅ 应用层多数已传(7 prompt + 4 fetcher + board_service + fdw_queries) |
| `apply_runtime_session_vars` | C 方案"开关" | ❌ **全仓库 0 处调用** |
**致命隐性 bug**:`apply_runtime_session_vars` 是激活 ETL 库 26 个视图的开关,但开关从未被打开 → ETL 库视图调 `app.business_date_now()` 拿不到 GUC → fallback 真实 today → **Neo 切 sandbox=2026-03-01 后,后端走 ETL 库 `app.v_*` 视图查询时,仍能看到 2026-05-05 的"未来"数据**
### 业务联系
- F1-5 主诉求"仅能看到沙箱设定日期及之前数据" — 当前没满足(因为 GUC 开关没开)
- W1-T1 看板沙箱接入(2026-05-04)— 走 A 方案在前端 isCurrentMonthFilter 处理,与本决策正交
- W1-T2 SCD2 视图统一(2026-05-04)— 走 C 方案,本决策对其有影响(SCD2 视图也读 GUC)
- Wave 2 P1-1 schema 迁移、P1-6 触发器合并 — 与 C 方案 zqyy_app 视图层延伸相关
### 修改影响
| 选项 | 改动 | 工作量 | 业务效果 |
|---|---|---|---|
| **a 全做(扩 zqyy_app)** | dispatcher 入口调 + zqyy_app 加 RLS 视图层(C 方案落地) | 大(2-3 天,涉及 RLS 双 schema 规则) | 两库都靠 GUC 自动保护 |
| **b 移除** | 删 `apply_runtime_session_vars` + 文档移除 C 方案 + ETL 库 26 视图改回不带 GUC | 中(回滚 C 方案,审计成本) | 只靠应用层 A 方案 |
| **c 混合(我推荐)** | dispatcher 入口调一次 `apply_runtime_session_vars` + zqyy_app 库继续 A 方案,**不新增 RLS 视图层** | 极小(1-2 行代码 + 几个调用点) | ETL 库 C 方案激活(自动保护)+ zqyy_app 应用层 A 方案(显式保护)— 两库都安全 |
### 推荐选项
**c 混合方案 — 纳入阶段 A**(F1-5 内做)
### 建议与理由
- ETL 库 C 方案是 2026-05-02 的**已完成资产**,丢掉浪费(b 方案)
- zqyy_app 库 RLS 视图层从无到有是**新建工作量**,且应用层 A 方案已做 80%(只剩 admin_service 的 `CURRENT_DATE`,在阶段 B1 已纳入),没必要重复建第二套保护(a 方案过度)
- c 方案只需要 dispatcher / batch executor 拿 DB 连接后调一次 `apply_runtime_session_vars(conn, ctx=ctx_snapshot)` → ETL 库 26 个视图**立即激活**
- 业务结果:Neo 切 sandbox 后,ETL 库视图自动裁剪未来数据,业务库由应用层显式传保护,**两库都安全**
- 副作用:`docs/prd/specs/P20-runtime-context-sandbox.md` 需要把 §6 中"zqyy_app 加 RLS 视图层"的规划项移除或标"永不做"
**Neo 反馈**:
*能不能完整且统一?符合规范化的项目架构设计,保持一致,后续也能形成规范延续下去。*
---
## D3. 测试覆盖是否纳入 F1-5
### 关联页面/接口
- `apps/backend/tests/`(`tests/tests/unit/``tests/tests/integration/`)
- `apps/backend/tests/test_runtime_context*.py`(**当前不存在**)
- `apps/backend/tests/test_admin_ai_*.py`(已存在但 0 处 sandbox 覆盖)
### 业务背景
P20 SPEC §8 验收标准 AC1-13 含"沙箱模式数据隔离 / namespace 前缀正确 / 业务日上界生效"等多项硬性要求。当前**全仓库 0 测试覆盖** sandbox/runtime_context:
- `apply_runtime_session_vars``namespace_ai_target_id``task_runtime_filter``business_date_upper_bound_sql` 全无 unit
- `_run_batch` 即使补完 executor,无 integration 测试验证 ctx_snapshot 不污染
- dispatcher 落 runtime 列无回归保护
### 冲突逻辑
- F1-5 是 P0-7 沙箱主线**必修**(P20 验收前提)
- 但调研发现 0 测试 → 无回归保护 → 上线后任何后续改动都可能悄无声息破坏 sandbox 隔离
- 这是 W1 findings F1-1 反馈"长事务幂等"同类风险:有规则没测试 = 规则形同虚设
### 业务联系
- W1 findings F1-1 长事务幂等:Neo 反馈"我们之前因没测试导致 mock/prod 偏差"(类似教训)
- F2-2 tests/ 入仓(Wave 5)— 测试需要先入仓才能跑 CI,但本地测试不需要
- 测试规范:`docs/_overview/03-test-spec.md` L1-L5 五级测试
- `.gitignore:71` 排除 tests/ → 本地写测试不能 commit(F2-2 待解决)
### 修改影响
| 选项 | 改动 | 工作量 |
|---|---|---|
| **a 纳入 F1-5(我推荐)** | 写 3 套测试:test_runtime_context.py(unit 13 API)/ test_admin_ai_batch_runtime.py(integration estimate→切模式→confirm 用快照)/ test_dispatcher_runtime.py(_run_step 落 runtime 列) | 1-2 小时(主要是 fixture 准备) |
| **b 留 Wave 2** | 现在不做,Wave 2 测试专项做 | 0(此次)+ Wave 2 半天 |
| **c 简化版纳入** | 只写 1 套 integration 测试(estimate→切模式→confirm 用快照),unit 留 Wave 2 | 30 分钟 |
### 推荐选项
**a 纳入 F1-5 阶段 A**
### 建议与理由
- 沙箱机制是 P0 主线,无测试上线极高风险
- 1-2 小时 vs 上线后回归 bug 抓虫 = 投入产出比极高
- F2-2 tests/ 入仓未做不阻塞:测试本地写本地跑,Wave 5 入仓时一并迁移
- W1 findings 教训告诉我们:不写测试的"机制"不是真机制
- 备选:如果时间紧,接受 c 方案(只 integration)
**Neo 反馈**:
*同意 a 纳入*
---
## D4. 走查 mock 数据(切 sandbox=2026-03-01)授权
### 关联页面/接口
- 后端:`apps/backend/app/routers/admin_runtime_context.py:95`(`PATCH /api/admin/runtime-context`)
- DB:`test_zqyy_app.biz.site_runtime_context`(测试库唯一 site `2790685415443269` 当前 `mode='live'`)
- W1-T8 §14 走查清单:`docs/prd/specs/P20-runtime-context-sandbox.md:487-566`
### 业务背景
W1-T8 §14 成果层走查需要在 test_zqyy_app **切 sandbox=2026-03-01**(与 W1-T1/T2 测试日期同步),实地验证:
- admin-web 顶部 sandbox 提示条显示
- 触发 1 次 app2_finance 后 `ai_run_logs.runtime_mode='sandbox'` + prompt JSON `current_time='2026-03-01 HH:MM'` + `ai_cache.target_id``sbx_*:` 前缀
- ETL 库 `app.v_*` 视图业务日上界生效(查不到 03-01 之后数据)
完成走查后**切回 live**。
### 冲突逻辑
- CLAUDE.md 测试与验证环境规范要求:"使用测试库,禁止连正式库" — 切 sandbox 是测试库操作,合规
- CLAUDE.md 不破坏原则:"不在测试库以外执行 DDL / TRUNCATE / DELETE" — 切 sandbox 是 UPDATE 不是 DDL,合规
- 但 mode 切换是状态改动,需要 Neo 显式授权
### 业务联系
- F1-5 走查必须先切 sandbox 才能验证(否则 live 模式下所有 sandbox 代码路径都不会触发)
- 切完 sandbox 不切回会污染后续测试(下次有人跑测试拿到的是"虚拟时间")
- audit_log 会记录切换动作(switch_runtime_context 内部已经写 audit)
### 修改影响
| 选项 | 改动 | 风险 |
|---|---|---|
| **a 授权 PATCH 端点切**(我推荐) | 调 `PATCH /api/admin/runtime-context` 切 sandbox=2026-03-01 + 走查完切回 live | 测试库改动,有 audit 记录 |
| **b 授权 SQL 直改** | `UPDATE biz.site_runtime_context ... WHERE site_id=2790685415443269` | 绕过 audit,不推荐 |
| **c 不授权,Neo 自切** | 等 Neo 手动切完 + 通知 Claude 跑走查 | 多一轮等待,但更稳 |
| **d 走查时再决定** | F1-5 改完之后再问授权 | 推迟决策,减少认知负担 |
### 推荐选项
**a 授权 PATCH 端点切 + 完成后切回 live**
### 建议与理由
- PATCH 端点是正式入口,自带 audit,合规
- 走查后立即切回 live 避免污染
- Claude 实施 F1-5 期间不需要切(F1-5 单元测试可用 mock RuntimeContext);只在 W1-T8 走查阶段切一次
- 切换是 idempotent 操作,失败可回滚
- 如果不放心,可以选 c(走查时 Neo 手动切)— 这是最保守的
**Neo 反馈**:
*授权 a*
---
## D5. commit 拆分
### 关联页面/接口
- 阶段 A(MVP):`admin_service.py` _run_batch + estimate snapshot / `admin_ai.py` 路由 / `app2_finance_prompt.py` 3 行 / `retry_trigger_job` SQL / `AIOperations.tsx` 提示条 / 新 migration `<日期>__ai_run_logs_runtime_index.sql`
- 阶段 B(漂移防御):`admin_service.py` 6 处 SQL CURRENT_DATE / `fdw_queries.py:113,2552` 兜底
- 测试:3 个新 test 文件(若 D3 选 a)
- SPEC 同步:`P20-runtime-context-sandbox.md` §10/§11/§15(若 D6 选纳入)
### 业务背景
F1-5 涉及 ~10 个文件改动,跨后端 / 前端 / DB / 测试 / 文档 5 类。如何拆 commit 影响:
- 回滚粒度(出问题能精准 revert)
- 主题清晰度(commit log 可读)
- review 难度(reviewer 一次看多少)
### 冲突逻辑
| 维度 | 1 个 commit | 2 个 commit | 3 个 commit |
|---|---|---|---|
| 主题 | F1-5 完整收口 | A 沙箱接入 / B 漂移防御 | DB / Backend / Frontend |
| 回滚 | 全或无 | 中粒度 | 细粒度 |
| 可读性 | 包大 | 中 | 多 |
CLAUDE.md 提倡"逻辑独立的改动各自 commit",但本次所有改动都属于 F1-5 单一主题。
### 业务联系
- F3-2C 之前拆 2 commit(A 处置 + B 防御 hook)— 因为是 2 个独立主题
- F1-5 是单一主题:让沙箱 batch-run 真正生效
- Wave 1 Day 1-4 多数是 1 个 commit / 任务
### 修改影响
| 选项 | 适用 |
|---|---|
| **A 1 个 commit(我推荐)** | 单一主题,回滚一气呵成,改动文件 ~10 个可控 |
| B 2 个 commit | A:核心接入 / B:漂移防御 — 主题略有差异 |
| C 3 个 commit | DB/Backend/Frontend — 跨层拆分,但每个 commit 不能独立运行(后端依赖 DB 索引,前端依赖后端字段) |
### 推荐选项
**A 1 个 commit:`fix(ai): F1-5 沙箱 batch-run 接入 runtime_context(含 P20 漂移防御 + GUC C 方案激活)`**
### 建议与理由
- F1-5 是单一主题,拆 commit 反而割裂上下文
- 改动文件 10 个可读,commit message 清楚说明
- 跨层依赖(DB 索引 → 后端代码 → 前端组件)不能各自独立运行,拆 commit 后中间状态不可用
- 历史经验:F3-2C 拆 2 commit 是因为有 2 个独立主题(内容拆分 vs 防御机制),F1-5 不一样
**Neo 反馈**:
*同意 1 个 commit*
---
## D6. P20 SPEC §11/§15/§10 文档同步更新
### 关联页面/接口
- `docs/prd/specs/P20-runtime-context-sandbox.md`
- §6 GUC C 方案路线(若 D2 选 c 混合,需要标"zqyy_app RLS 视图层不做")
- §10 跨模块覆盖矩阵(app2/app2a 标记从 "?" 改 ✅)
- §11 已知遗漏(删除 batch-run 相关项)
- §15 变更记录(追加 2026-05-05 F1-5)
- 可能 §14 走查归档章节(走查后追加结果链接)
### 业务背景
P20 SPEC 是沙箱机制的权威设计文档。F1-5 是 P20 落地的"最后一公里",完成后 SPEC 必须同步:
- §10 矩阵反映现状(否则未来 reviewer 看 SPEC 仍会以为有缺失)
- §11 已知遗漏要把 batch-run 移除(已修复)
- §15 变更记录追加里程碑
不同步会导致 SPEC 和实际代码漂移,违反 W1 findings F2-1 教训(OpenAPI 28 天 stale 同类问题)。
### 冲突逻辑
- CLAUDE.md 数据库 Schema 变更规则:"必须同步 docs/database/" — 类似规则适用 SPEC
- W1 findings F2-1 教训:文档与代码漂移会让审计失效
### 业务联系
- F2-1B 防御机制 hook(已生效):router 改动会提醒抓 OpenAPI — 类似机制对 SPEC 缺失
- W1 findings F1-1 长事务幂等:文档没说 → 没人能验收
- 后续审计文档需要引用 P20 SPEC 当前状态
### 修改影响
| 选项 | 改动 | 工作量 |
|---|---|---|
| **a 纳入 F1-5 commit(我推荐)** | F1-5 同 commit 改 §6/§10/§11/§15 | 10 分钟(纯文档) |
| b 单独 commit | F1-5 后另起 commit "docs(spec): P20 同步 F1-5 落地" | 多一个 commit |
| c 留 Wave 5 文档收尾 | 30+ 天 SPEC 漂移,期间任何人看到 SPEC 都误判 | 高漂移风险 |
### 推荐选项
**a 纳入 F1-5 commit**
### 建议与理由
- SPEC owner 视角同步,不做的话 SPEC 永远和实际漂移
- 10 分钟工作量,不值得拆 commit
- 跟 D5(1 个 commit)相容
- 跟 F3-2C 的 ai-app-prompts.md A 处置同时改造 SPEC 一样的逻辑
**Neo 反馈**:
*同意 a 纳入*
---
## 总览决策表(Neo 自填)
| # | 决策项 | Claude 推荐 | Neo 决定 | 备注 |
|---|---|---|---|---|
| D1 | batch executor 方式 | Semaphore(3) + asyncio.gather + return_exceptions + ctx_snapshot | | |
| D2 | GUC C 方案处置 | 混合(纳入阶段 A) | | |
| D3 | 测试覆盖 | 纳入(阶段 A) | | |
| D4 | 走查 mock 授权 | 授权 PATCH 切 + 完成切回 | | |
| D5 | commit 拆分 | 1 个统一 commit | | |
| D6 | SPEC 同步 | 纳入 F1-5 commit | | |
---
## 已确认项(无需决策,记录在案)
| 项 | 决策 | 来源 |
|---|---|---|
| Q1 阶段 A+B 一起做 | ✅ 同意 | Neo 5/5 反馈 |
| Q4 子 1 — 不新建 sandbox_batch_runs 表,复用 sandbox_instance_id | ✅ 同意 | Neo 5/5 反馈 |
| Q4 子 3 — ai_run_logs 索引随 F1-5 一起补 | ✅ 同意 | Neo 5/5 反馈 |
| Q4 子 4 — BD_manual_biz_registry_tables.md 保持分离 | ✅ 同意 | Neo 5/5 反馈 |
---
## 关联
- F1-5 业务故事卡:[`00-W1-findings-stories.md`](00-W1-findings-stories.md)(F1-5 章节)
- F1-5 反馈响应:[`01-W1-findings-response.md`](01-W1-findings-response.md)
- P20 SPEC:[`docs/prd/specs/P20-runtime-context-sandbox.md`](../../prd/specs/P20-runtime-context-sandbox.md)
- W1-T7 PRD 批 1:[`docs/_overview/admin-api-prd/batch1-runtime-context-and-ai.md`](../admin-api-prd/batch1-runtime-context-and-ai.md)
- 调研子代理报告(本次会话内,未单独入仓)