# admin-web 沙箱 UI 手工验证清单

> 配套自动化报告：[`2026-05-02__sandbox_admin_web_verify_report.md`](2026-05-02__sandbox_admin_web_verify_report.md)
> 自动化已 PASS 15/15，本文件覆盖必须人工 UI 操作 / 视觉确认的部分。
>
> **重要**：admin-web 没有"财务/客户/助教看板"，沙箱在 admin-web 端的表现集中在
> **沙箱开关本身（RuntimeContext 页）+ AI 调用 / 任务隔离 / 触发器调度**。
> 业务看板的「不看未来」效果，应到**小程序**端验证。

## 0. 准备

1. 后端启动：

   ```bash
   cd apps/backend
   uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
   ```
2. admin-web 启动：

   ```bash
   cd apps/admin-web
   pnpm dev   # 默认 http://localhost:5173
   ```
3. 用 **super_admin** 账号登录（`/settings/runtime-context` 仅超级管理员可见）。
4. 选择测试门店（site_id 例：`2790685415443269`，已在 `test_zqyy_app.biz.sites` 存在）。

---

## 1. 切换沙箱（核心入口）

**路径**：左侧菜单 → 系统设置 → 业务运行上下文 / 沙箱（`/settings/runtime-context`）

### 操作步骤

1. 选择门店（`2790685415443269`）。
2. 点击「切换上下文」按钮。
3. 选择 `mode = sandbox`，`sandbox_date = 2025-09-01`，`sandbox_instance_id = (留空，自动生成)`，`status = active`。
4. 点击 **确认切换**。

### 期望显示

| UI 元素 | 期望 | 验签方法 |
|---|---|---|
| 提交按钮提交后 | 主弹窗自动关闭 | 不应停留在表单 |
| 弹出 **执行步骤** 弹窗 | 列出迁移步骤（`runtime_context_upserted` / `pending_jobs_cancelled` / `biz_triggers_unchanged` / `runtime_cache_purged` 等），每步有 ✅ | 见 R1 设计文档 |
| 顶部 Alert | 提示「按门店隔离的业务时钟」+ 「读取层修复进行中」warning | Alert 在最新版本显示 |
| 表格行（该 site_id） | mode=sandbox，sandbox_date=2025-09-01，sandbox_instance_id 为 `sbx_...` | 直接看 |

### 数据库验签（PowerShell）

```powershell
psql "$env:TEST_APP_DB_DSN" -c "SELECT mode, sandbox_date, sandbox_instance_id, status FROM biz.site_runtime_context WHERE site_id = 2790685415443269"
```
期望：`sandbox | 2025-09-01 | sbx_xxxx | active`。

---

## 2. AI 调用明细（沙箱 prompt 内 current_time）

**路径**：日志调试 → AI 调用明细（`/logs/ai-run-logs`）

> **目的**：验证沙箱模式下 AI 真实生成的 prompt 里 `current_time` / `当前时间` 字段是 `sandbox_date`，不是真实今天。

### 操作步骤（必须有 sandbox 切换后产生的新调用）

1. 沙箱切换好后，**触发一次 AI 调用**（任选一种）：
   - **路径 A**：AI 管理 → 手动操作（`/ai/operations`） → Card 3「批量执行」 → 选 App3 + 输入一个 member_id（例如 `2854141942400709`） → 预估 → 确认执行。等待 5s（AI 实际调用约 2–10s）。
   - **路径 B**：AI 管理 → 手动操作 → Card 1「触发事件」（如有） → 选 `consumption` → 填 site_id + member_id → 触发。
   - **路径 C**（最快不依赖 AI）：在 admin-web 触发任意 App2 重跑（`/ai/operations` 的「手动重跑」），用任意 trigger_job_id。
2. 切到 AI 调用明细页，按 site_id 筛选，第一条应是刚才触发的那条。
3. **点击该行**，弹出 Drawer。

### 期望显示（Drawer）

| 字段 | 期望 |
|---|---|
| App 类型 | 你触发的那个 App |
| 触发方式 | `manual` 或 `event` |
| Tokens / 延迟 | 有数值 |
| 状态 | `success`（也可能 `circuit_open` / `failed`，看实际情况；本验证关注 prompt 内容） |
| **Request Prompt** | 展开 JSON，找 `"current_time"`（App3-7） 或 `"当前时间"`（App2/2a） |
| **`current_time` 的值** | `2025-09-01 HH:MM`（HH:MM 是真实当前钟点） — 沙箱生效 ✅ |
| **`当期日期范围`**（仅 App2/2a） | 形如 `2025-09-01 ~ 2025-09-01`，**末日 ≤ 2025-09-01** ✅ |

### 关键反例（沙箱失效特征）

- ❌ `current_time = 2026-05-02` → 说明 prompt 没走 RuntimeContext，回归 bug
- ❌ `当期日期范围 = 2026-05-01 ~ 2026-05-02` → 说明 board_service 没传 ref_date

---

## 3. AIOperations Card 2 缓存命名隔离

**路径**：AI 管理 → 手动操作（`/ai/operations`）

### 操作步骤

1. Card 2「缓存失效」：选 App 类型 `app7_customer_analysis` + member_id（任意） + site_id（沙箱中的）。
2. 点击 **失效**。
3. 看返回提示：「失效成功（受影响 X 条）」。

### 期望

- X 应该是 0 或者很小的数（因为 sandbox 实例下还没产生 cache 记录）。
- **关键**：sandbox 模式失效**不会触及 live 模式的 cache**。
- DB 验签：

  ```sql
  -- live 缓存仍在
  SELECT COUNT(*) FROM biz.ai_cache
  WHERE site_id = 2790685415443269
    AND target_id = '<your_member_id>'         -- 不带 sbx_ 前缀
    AND cache_type = 'app7_customer_analysis'
    AND COALESCE(runtime_mode, 'live') = 'live';

  -- sandbox 缓存：target_id 带前缀
  SELECT target_id FROM biz.ai_cache
  WHERE site_id = 2790685415443269
    AND target_id LIKE 'sbx_%'
    AND cache_type = 'app7_customer_analysis';
  ```

  期望：`target_id` 形如 `sbx_xxxx:<member_id>`。

---

## 4. TaskManager 任务队列 / 历史

**路径**：小程序任务管理 → 定时任务 / 转移日志（`/task-engine/*`）；以及通用 `/tasks` 队列

### 期望

| Tab | 期望 |
|---|---|
| 队列 / 调度 | 看到的是**调度任务本身**（trigger_jobs），不分 sandbox/live；**不应**因为切了沙箱就空白 |
| 历史 | 看到的是**所有 trigger 执行历史**，包含 live 与 sandbox 两套写入的数据 |

### 数据库验签

```sql
-- coach_tasks 同时有 live / sandbox 两套
SELECT runtime_mode, sandbox_instance_id, COUNT(*)
FROM biz.coach_tasks
WHERE site_id = 2790685415443269
GROUP BY runtime_mode, sandbox_instance_id
ORDER BY 1, 2;
```

期望：会看到 `live | live | N` 行，可能还有 `sandbox | sbx_xxxx | M` 行（如果触发过 task_generator）。

> 提示：如果想看 sandbox 实例**专属**任务列表，从后端 `/api/admin/task-engine/...` 上调试也行；
> 但当前 admin-web TaskManager 页面没有 sandbox/live 切换 UI，所有数据按 trigger_job 维度展示。

---

## 5. TriggerManager（不应被 sandbox 暂停）

**路径**：触发器管理（`/triggers`）

### 期望

| Tab | 期望 |
|---|---|
| 全部（all） | 9 条（biz / ai / etl 三类）全部 status=active 或 success，**没有 paused_by_sandbox** |
| AI tab | AI 触发器全部 active；切沙箱**不影响** |
| 业务（biz） | 业务触发器全部 active |
| ETL | scheduled_tasks 全部 active |

### 关键反例（曾经的 R1 bug）

- ❌ 切沙箱后看到 `status = paused_by_sandbox` → R1 改造前的问题，已在 `admin_runtime_context.py` 移除该逻辑（`biz.trigger_jobs` 是全局表，不应按 site 维度暂停）。

### 数据库验签

```sql
SELECT status, COUNT(*) FROM biz.trigger_jobs GROUP BY status;
```
期望：**没有任何** `paused_by_sandbox`。

---

## 6. AIDashboard（按真实时间）

**路径**：AI 管理 → 总览（`/ai/dashboard`）

### 期望

| 指标 | 期望 |
|---|---|
| **今日调用次数 / 成功率 / Tokens** | 按**真实今天**（2026-05-02）统计，**不应**因为沙箱切到 2025-09-01 而骤降 |
| **预算消耗** | 按真实月份累计 |
| **App 健康度** | 按最近真实数据 |
| **7 天趋势** | 真实 7 天 |

### 关键说明

- AIDashboard 的"今日"窗口走的是 `CURRENT_DATE`（写入 ai_run_logs 时也是真实系统时间），与 sandbox 解耦。
- 沙箱只影响 **prompt 内容** 和 **业务读取**，不影响 **运维监控指标**（这是设计共识）。

---

## 7. AIRunLogs / AITriggerJobs 列表（也按真实时间）

**路径**：日志调试 → AI 调用明细（`/logs/ai-run-logs`）+ AI 管理 → 调度历史（`/ai/trigger-jobs`）

### 期望

| 列 | 期望 |
|---|---|
| `created_at` | 真实时间（2026-05-02 当前钟点） |
| `finished_at` | 真实时间 |

**AI 写入时间不被沙箱影响**——仅 prompt 内容 / 业务查询窗口受沙箱影响。

---

## 8. 还原 live

完成验证后，**必须还原**：

1. 系统设置 → 业务运行上下文 / 沙箱 → 选回 `mode = live`，提交。
2. 数据库验签：

   ```sql
   SELECT mode, sandbox_date, sandbox_instance_id FROM biz.site_runtime_context
   WHERE site_id = 2790685415443269;
   ```

   期望：`live | NULL | NULL`。

> 如果想让脚本自动还原，本验证清单的所有 sandbox 写入会保留以备审计；但生产环境上线前**务必**清回 live。

---

## 9. 一键自动化（不必手工跑的部分）

```powershell
# 沙箱端到端（覆盖 RLS 视图 + 业务 service + AI prompt 时间锚）
python tools/db/verify_sandbox_end_to_end.py --sandbox-date 2025-09-01

# admin-web 后端 API / prompt 构建 / 任务隔离
python tools/db/verify_admin_web_sandbox.py --sandbox-date 2025-09-01
```

两个脚本会自动把测试库 sandbox 切换 → 跑断言 → 还原 live → 输出 markdown 报告。

---

## 10. 检查项汇总（手工 + 自动）

| 类别 | 项目 | 自动化 | 手工 |
|---|---|:---:|:---:|
| RuntimeContext API | mode/sandbox_date/business_date/sandbox_instance_id | ✅ 5/5 | ✅ 主弹窗关闭、Steps 弹窗 |
| AIRunLogs prompt | App2 当前时间 / App3-7 current_time | ✅ App2 PASS | ✅ Drawer 看 Request Prompt JSON |
| AIRunLogs 列表 | created_at 按真实时间 | ✅ | — |
| AIOperations 缓存隔离 | namespace_ai_target_id / runtime_insert_columns | ✅ 4/4 | ✅ Card 2 失效操作返回值 |
| TaskManager 任务隔离 | task_runtime_filter SQL + 实查 | ✅ 5/5 | ✅ 任务列表分布 |
| TriggerManager 全局触发器 | 不应 paused_by_sandbox | ✅ 2/2 | ✅ 4 个 Tab 各看一眼 |
| AIDashboard 真实时间 | get_dashboard 按真实窗口 | ✅ 3/3 | ✅ 数字不应骤降 |

**自动化**：15/15 PASS（详见同目录 `*_admin_web_verify_report.md`）。
**手工**：按本清单 1–8 节逐项核对，预估 10–15 分钟。
