docs: V1 演示准备实施计划(4 Chunk / 6 Task)
Chunk 1: Token 刷新竞态修复(原子 CAS via token_hash) Chunk 2: 告警/AI/health_manager 链路验证 Chunk 3: 演示数据预置脚本(张建国 + 25 背景患者) Chunk 4: 端到端 DRY RUN(7 场景验证)
This commit is contained in:
476
docs/superpowers/plans/2026-05-09-v1-demo-preparation-plan.md
Normal file
476
docs/superpowers/plans/2026-05-09-v1-demo-preparation-plan.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# V1 客户演示准备 — 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 修复已知 CRITICAL 问题,预置演示数据,完成 DRY RUN 验证,确保 V1 客户演示 7 个场景端到端无阻塞。
|
||||
|
||||
**Architecture:** 按依赖关系分 6 个 Task:先修 CRITICAL(Token 竞态),再验证关键链路(告警、AI),然后预置数据,最后全链路冒烟。每个 Task 独立可提交。
|
||||
|
||||
**Tech Stack:** Rust (SeaORM + Axum), TypeScript/React (Web 前端), SQL (数据预置), Taro (小程序)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-09-v1-customer-demo-plan-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| 操作 | 文件 | 职责 |
|
||||
|------|------|------|
|
||||
| Modify | `crates/erp-auth/src/service/token_service.rs:156-176` | revoke 改为原子操作 |
|
||||
| Modify | `crates/erp-auth/src/service/auth_service.rs:187-258` | refresh 流程使用原子 revoke |
|
||||
| Create | `crates/erp-server/tests/integration/auth_concurrent_tests.rs` | 并发刷新测试 |
|
||||
| Create | `scripts/demo-seed.sql` | 演示数据预置脚本 |
|
||||
| Verify | `crates/erp-health/src/service/seed.rs` | 确认告警规则覆盖演示场景 |
|
||||
| Verify | `apps/web/src/pages/health/components/LabReportsTab.tsx:36-57` | 确认 AI 触发按钮可用 |
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: Token 刷新竞态修复
|
||||
|
||||
### Task 1: 修复 Token 刷新并发竞态(CRITICAL)
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-auth/src/service/token_service.rs` — 新增 `revoke_by_hash_atomic` 方法
|
||||
- Modify: `crates/erp-auth/src/service/auth_service.rs:193-197` — refresh 中改用原子操作
|
||||
- Create: `crates/erp-server/tests/integration/auth_concurrent_tests.rs`
|
||||
|
||||
**设计说明:** JWT claims 中没有 token 数据库 ID(`id` 列),只有 `sub`(user_id) 和 `tid`(tenant_id)。因此原子 CAS 应该使用 `token_hash` 作为匹配条件——先用 JWT 解码获取原始 token,计算 SHA-256 哈希,再用 `UPDATE WHERE token_hash = ? AND revoked_at IS NULL` 做原子操作。这样不需要修改 JWT 结构。
|
||||
|
||||
- [ ] **Step 1: 在 token_service.rs 新增 `revoke_by_hash_atomic` 方法**
|
||||
|
||||
在 `crates/erp-auth/src/service/token_service.rs` 第 176 行(`revoke_token` 方法之后)新增:
|
||||
|
||||
```rust
|
||||
/// 原子操作:通过 token_hash 验证并撤销 refresh token。
|
||||
/// 如果 token 已被撤销(rows_affected == 0),返回 AuthError::TokenRevoked。
|
||||
pub async fn revoke_by_hash_atomic(
|
||||
db: &DatabaseConnection,
|
||||
token_hash: &str,
|
||||
user_id: Uuid,
|
||||
) -> AuthResult<()> {
|
||||
use user_token::Entity as UserToken;
|
||||
let result = UserToken::update_many()
|
||||
.col_expr(
|
||||
user_token::Column::RevokedAt,
|
||||
sea_orm::sea_query::Expr::value(Some(chrono::Utc::now().naive_utc())),
|
||||
)
|
||||
.filter(user_token::Column::TokenHash.eq(token_hash))
|
||||
.filter(user_token::Column::UserId.eq(user_id))
|
||||
.filter(user_token::Column::RevokedAt.is_null())
|
||||
.exec(db)
|
||||
.await
|
||||
.map_err(|e| AuthError::Validation(e.to_string()))?;
|
||||
if result.rows_affected == 0 {
|
||||
return Err(AuthError::TokenRevoked);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
需要新增导入:`use sea_orm::sea_query::Expr;`(参考 `consultation_service.rs:683` 的模式)
|
||||
|
||||
- [ ] **Step 2: 改造 auth_service.rs 的 refresh 流程**
|
||||
|
||||
在 `crates/erp-auth/src/service/auth_service.rs:193-197`,将当前的 validate + revoke 两步替换:
|
||||
|
||||
```rust
|
||||
// 旧代码(第 193-197 行):
|
||||
// let claims = TokenService::validate_refresh_token(&self.token, &self.db).await?;
|
||||
// TokenService::revoke_token(&self.db, &claims.token_id, claims.user_id).await?;
|
||||
|
||||
// 新代码:
|
||||
// 1. JWT 解码获取 claims(不查数据库)
|
||||
let claims = TokenService::decode_refresh_token(&self.token)?;
|
||||
// 2. 计算 token 的 SHA-256 哈希
|
||||
let token_hash = TokenService::hash_token(&self.token);
|
||||
// 3. 原子操作:通过 hash 验证 + 撤销(CAS)
|
||||
TokenService::revoke_by_hash_atomic(&self.db, &token_hash, claims.sub.parse()?).await?;
|
||||
// 4. 后续:查询用户角色权限(第 200-201 行,不变)
|
||||
```
|
||||
|
||||
注意:需要确认 `decode_refresh_token`(仅 JWT 解码)和 `hash_token`(SHA-256 计算)是否已是公开方法。如果 `validate_refresh_token` 内部已有这些逻辑,需要拆分为独立方法。
|
||||
|
||||
- [ ] **Step 3: 编译检查**
|
||||
|
||||
Run: `cargo check --package erp-auth`
|
||||
Expected: 编译通过,无错误
|
||||
|
||||
- [ ] **Step 4: 写并发刷新测试**
|
||||
|
||||
在 `crates/erp-server/tests/integration/auth_concurrent_tests.rs` 中:
|
||||
|
||||
```rust
|
||||
use crate::test_db::TestDb;
|
||||
use erp_auth::service::auth_service::AuthService;
|
||||
use erp_core::config::JwtConfig;
|
||||
|
||||
async fn setup_test_user(db: &TestDb) -> (Uuid, String, String) {
|
||||
// 创建测试用户,返回 (user_id, access_token, refresh_token)
|
||||
// 复用现有集成测试中的用户创建逻辑
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_refresh_rotates_token() {
|
||||
let db = TestDb::new().await;
|
||||
let (_user_id, _, refresh_token) = setup_test_user(&db).await;
|
||||
let jwt_config = JwtConfig::default();
|
||||
|
||||
let svc = AuthService::new(db.conn(), &jwt_config);
|
||||
// 第一次 refresh → 成功
|
||||
let result = svc.refresh(&refresh_token).await;
|
||||
assert!(result.is_ok(), "第一次 refresh 应成功");
|
||||
let new_tokens = result.unwrap();
|
||||
// 用旧 refresh_token 再次 refresh → 必须失败
|
||||
let result2 = svc.refresh(&refresh_token).await;
|
||||
assert!(result2.is_err(), "旧 token 必须不可用");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_refresh_token_reuse() {
|
||||
let db = TestDb::new().await;
|
||||
let (_user_id, _, refresh_token) = setup_test_user(&db).await;
|
||||
let jwt_config = JwtConfig::default();
|
||||
|
||||
let svc = AuthService::new(db.conn(), &jwt_config);
|
||||
let token_clone = refresh_token.clone();
|
||||
let svc_clone = // 需要确认 AuthService 是否可 Clone 或用 Arc
|
||||
|
||||
// 使用 tokio::spawn 并发发两个 refresh
|
||||
let handle1 = tokio::spawn(async move { svc.refresh(&refresh_token).await });
|
||||
let handle2 = tokio::spawn(async move { svc_clone.refresh(&token_clone).await });
|
||||
|
||||
let r1 = handle1.await.unwrap();
|
||||
let r2 = handle2.await.unwrap();
|
||||
|
||||
// 恰好一个成功、一个失败
|
||||
let ok_count = [&r1, &r2].iter().filter(|r| r.is_ok()).count();
|
||||
assert_eq!(ok_count, 1, "并发 refresh 中恰好一个成功,另一个失败");
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 运行全部认证测试**
|
||||
|
||||
Run: `cargo test --package erp-auth`
|
||||
Expected: 全部通过
|
||||
|
||||
Run: `cargo test --package erp-server --test integration auth`
|
||||
Expected: 全部通过
|
||||
|
||||
Run: `cargo test --package erp-server --test integration auth_concurrent -- --nocapture`
|
||||
Expected: 两个测试 PASS
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/erp-auth/src/service/token_service.rs \
|
||||
crates/erp-auth/src/service/auth_service.rs \
|
||||
crates/erp-server/tests/integration/auth_concurrent_tests.rs
|
||||
git commit -m "fix(auth): 修复 Token 刷新并发竞态条件
|
||||
|
||||
使用原子 CAS(UPDATE WHERE token_hash = ? AND revoked_at IS NULL)
|
||||
替代先查后改的非原子操作,防止同一 refresh token 被并发使用两次。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2: 演示链路验证
|
||||
|
||||
### Task 2: 验证告警链路(场景 5 依赖)
|
||||
|
||||
**Files:**
|
||||
- Verify: `crates/erp-health/src/service/seed.rs` — 确认告警规则
|
||||
- Verify: `apps/web/src/pages/health/AlertDashboard.tsx:51` — 确认权限码
|
||||
- Verify: `crates/erp-health/src/handler/alert_handler.rs:82-115` — 确认操作端点
|
||||
|
||||
- [ ] **Step 1: 启动后端服务**
|
||||
|
||||
Run: `cd crates/erp-server && cargo run`
|
||||
Expected: 服务无 panic 启动在 localhost:3000
|
||||
|
||||
- [ ] **Step 2: 查询已有告警规则**
|
||||
|
||||
Run: `curl -s -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:3000/api/v1/health/alert-rules | jq '.data.items[] | {name, metric, operator, threshold}'`
|
||||
|
||||
Expected: 返回 10 条默认规则,包括:
|
||||
- 收缩压偏高 (>=140)
|
||||
- 收缩压危急 (>=180)
|
||||
|
||||
场景 5 需要"张大爷录入血压 168 触发告警"→ 使用已有的"收缩压危急 >=180"不够,需要调整场景 5 话术用血压 185,或添加一条 >=160 的规则。
|
||||
|
||||
- [ ] **Step 3: 手动测试告警触发**
|
||||
|
||||
```
|
||||
1. 以 nurse1 登录 Web
|
||||
2. 找到张大爷患者详情页
|
||||
3. 录入体征:收缩压 185 / 舒张压 95
|
||||
4. 切到告警仪表盘页面
|
||||
5. 确认出现告警条目
|
||||
6. 点击「确认」→ 状态变为已确认
|
||||
7. 点击「处理」→ 输入备注 → 状态变为已处理
|
||||
```
|
||||
|
||||
Expected: 全流程无 403、无 500
|
||||
|
||||
- [ ] **Step 4: 记录验证结果**
|
||||
|
||||
在文件头部注释验证结果。如果告警权限码正确(`health.alerts.manage`),记录为 ✅。
|
||||
如果发现任何问题,记录具体报错信息,新建 Task 修复。
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 验证 AI 分析触发(场景 2 依赖)
|
||||
|
||||
**Files:**
|
||||
- Verify: `apps/web/src/pages/health/components/LabReportsTab.tsx:177-183`
|
||||
- Verify: `apps/web/src/api/ai/analysisSse.ts`
|
||||
|
||||
- [ ] **Step 1: 确认 Ollama 模型就绪**
|
||||
|
||||
Run: `ollama list`
|
||||
Expected: 输出包含 `qwen3:4b`
|
||||
|
||||
如果没有:`ollama pull qwen3:4b`
|
||||
|
||||
- [ ] **Step 2: 手动触发 AI 分析**
|
||||
|
||||
```
|
||||
1. 以 admin 登录 Web
|
||||
2. 进入张大爷患者详情页
|
||||
3. 切到「化验报告」Tab
|
||||
4. 找到一条化验报告
|
||||
5. 点击「AI 解读」按钮
|
||||
6. 等待 SSE 流式输出
|
||||
```
|
||||
|
||||
Expected: AI 分析结果流式显示,无 500 错误
|
||||
|
||||
如果 AI 解读按钮不存在或化验报告为空 → 使用预案(预置截图),在脚本中标注
|
||||
|
||||
- [ ] **Step 3: 预置 AI 分析截图(预案)**
|
||||
|
||||
如果 AI 分析成功:截图保存到 `docs/demo/screenshots/ai-analysis.png`
|
||||
如果 AI 分析失败:在实施计划中标注使用备用话术
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 验证 health_manager 测试账号
|
||||
|
||||
**Files:**
|
||||
- Verify: 数据库 `users` 表
|
||||
- Reference: `crates/erp-server/migration/src/m20260506_000125_restructure_menus_and_roles.rs:123-126`
|
||||
|
||||
- [ ] **Step 1: 查询 health_manager 角色是否存在**
|
||||
|
||||
Run: `docker exec erp-postgres psql -U erp -c "SELECT id, name, code FROM roles WHERE code = 'health_manager'"`
|
||||
|
||||
Expected: 返回 1 行
|
||||
|
||||
- [ ] **Step 2: 查询是否有测试用户关联此角色**
|
||||
|
||||
Run: `docker exec erp-postgres psql -U erp -c "SELECT u.username, r.code FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id WHERE r.code = 'health_manager'"`
|
||||
|
||||
Expected: 返回至少 1 个用户
|
||||
|
||||
如果没有用户:需要通过 Web 管理界面创建一个 `health_mgr` 用户并分配 `health_manager` 角色
|
||||
|
||||
- [ ] **Step 3: 验证 health_manager 用户可登录**
|
||||
|
||||
用 health_manager 用户名 + `Admin@2026` 密码尝试登录 Web 端。
|
||||
Expected: 成功登录,工作台显示「任务工作台」
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3: 演示数据预置
|
||||
|
||||
### Task 5: 编写演示数据预置脚本
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/demo-seed.sql`
|
||||
|
||||
脚本目标:一键预置以下数据(幂等,可重复执行):
|
||||
|
||||
- 张建国患者档案 + 2 份历史化验单(肌酐 88→102)
|
||||
- 20-30 个背景患者(让仪表盘有数据)
|
||||
- 若干随访任务/告警记录(让仪表盘统计有意义)
|
||||
- 3 篇 CKD 健康科普文章
|
||||
- 收缩压 >=160 告警规则(如 seed 中没有)
|
||||
|
||||
- [ ] **Step 1: 编写 SQL 脚本骨架**
|
||||
|
||||
在 `scripts/demo-seed.sql` 中:
|
||||
|
||||
```sql
|
||||
-- HMS V1 Demo Data Seed
|
||||
-- 用法: docker exec -i erp-postgres psql -U erp < scripts/demo-seed.sql
|
||||
-- 幂等:使用 ON CONFLICT DO NOTHING
|
||||
|
||||
-- 1. 确保租户 ID(从现有租户获取)
|
||||
-- 2. 张建国患者档案
|
||||
-- 3. 2 份历史化验单(3 个月前 肌酐 88,1 个月前 肌酐 102)
|
||||
-- 4. 20 个背景患者(随机姓名,基础体征数据)
|
||||
-- 5. 若干随访任务(不同状态:pending/completed)
|
||||
-- 6. 若干告警记录(不同状态:pending/acknowledged/resolved)
|
||||
-- 7. 3 篇 CKD 科普文章
|
||||
-- 8. 收缩压 >=160 告警规则
|
||||
```
|
||||
|
||||
注意:所有 INSERT 需包含 `tenant_id`、`created_at`、`updated_at`、`created_by`、`updated_by`、`version`、`id`(UUID v7)字段。参考现有 Entity 的字段结构。
|
||||
|
||||
- [ ] **Step 2: 编写张建国患者 + 化验单数据**
|
||||
|
||||
```sql
|
||||
-- 患者档案
|
||||
INSERT INTO patients (id, tenant_id, name, gender, birth_date, phone, ...)
|
||||
VALUES (
|
||||
'019dcd34-bc4d-72c1-8c19-77ce1f4839d6', -- 使用已知测试患者 ID
|
||||
(SELECT id FROM tenants LIMIT 1),
|
||||
'张建国', 'male', '1961-03-15', '13800138001', ...
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 化验单 1:3 个月前 肌酐 88
|
||||
INSERT INTO lab_reports (...) VALUES (...) ON CONFLICT (id) DO NOTHING;
|
||||
-- 化验单 1 的 items:肌酐 88 μmol/L
|
||||
INSERT INTO lab_report_items (...) VALUES (...) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 化验单 2:1 个月前 肌酐 102
|
||||
INSERT INTO lab_reports (...) VALUES (...) ON CONFLICT (id) DO NOTHING;
|
||||
-- 化验单 2 的 items:肌酐 102 μmol/L
|
||||
INSERT INTO lab_report_items (...) VALUES (...) ON CONFLICT (id) DO NOTHING;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 编写背景患者批量数据**
|
||||
|
||||
使用 SQL generate_series 生成 20-30 个虚拟患者:
|
||||
|
||||
```sql
|
||||
INSERT INTO patients (id, tenant_id, name, gender, birth_date, ...)
|
||||
SELECT
|
||||
gen_random_uuid(),
|
||||
(SELECT id FROM tenants LIMIT 1),
|
||||
'测试患者' || i,
|
||||
CASE WHEN i % 2 = 0 THEN 'male' ELSE 'female' END,
|
||||
CURRENT_DATE - (30 + (i * 37) % 50) * INTERVAL '1 year',
|
||||
...
|
||||
FROM generate_series(1, 25) AS i
|
||||
ON CONFLICT DO NOTHING;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 编写随访任务和告警记录**
|
||||
|
||||
为背景患者生成不同状态的随访任务和告警记录,让仪表盘统计有意义。
|
||||
|
||||
- [ ] **Step 5: 编写科普文章和告警规则**
|
||||
|
||||
```sql
|
||||
-- 3 篇 CKD 科普文章
|
||||
INSERT INTO articles (title, content, category, status, ...) VALUES
|
||||
('慢性肾病患者的饮食指南', '...', 'nutrition', 'published', ...),
|
||||
('CKD 患者运动建议', '...', 'exercise', 'published', ...),
|
||||
('慢性肾病常用药物说明', '...', 'medication', 'published', ...)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 收缩压 >=160 告警规则(如果 seed 中没有)
|
||||
INSERT INTO alert_rules (name, metric, operator, threshold, ...)
|
||||
VALUES ('收缩压偏高(演示用)', 'systolic_bp', '>=', 160, ...)
|
||||
ON CONFLICT DO NOTHING;
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 执行脚本验证**
|
||||
|
||||
Run: `docker exec -i erp-postgres psql -U erp < scripts/demo-seed.sql`
|
||||
Expected: 无错误,所有 INSERT 成功或 ON CONFLICT 跳过
|
||||
|
||||
- [ ] **Step 7: 验证数据完整性**
|
||||
|
||||
```
|
||||
1. 查询张建国患者:SELECT * FROM patients WHERE name = '张建国'
|
||||
2. 查询化验单数量:SELECT count(*) FROM lab_reports WHERE patient_id = ...
|
||||
3. 查询背景患者数:SELECT count(*) FROM patients WHERE name LIKE '测试患者%'
|
||||
4. 查询文章数:SELECT count(*) FROM articles WHERE status = 'published'
|
||||
Expected: 1 张建国 + 2 化验单 + 25 背景患者 + 3 文章
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add scripts/demo-seed.sql
|
||||
git commit -m "chore(demo): V1 演示数据预置脚本
|
||||
|
||||
一键预置张建国患者+化验单+25背景患者+随访+告警+科普文章。
|
||||
幂等设计,可重复执行。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4: 全链路 DRY RUN
|
||||
|
||||
### Task 6: 端到端 DRY RUN(7 个场景)
|
||||
|
||||
**前置条件:** Task 1-5 全部完成
|
||||
|
||||
- [ ] **Step 1: 环境启动检查**
|
||||
|
||||
```bash
|
||||
# 1. PostgreSQL
|
||||
docker exec erp-postgres pg_isready
|
||||
|
||||
# 2. 后端
|
||||
curl -s http://localhost:3000/api/v1/auth/health | jq .
|
||||
|
||||
# 3. Web 前端
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:5174
|
||||
|
||||
# 4. Ollama
|
||||
ollama list | grep qwen3
|
||||
```
|
||||
|
||||
Expected: 全部 200/ready
|
||||
|
||||
- [ ] **Step 2: 场景 1 — 护士建档**
|
||||
|
||||
登录 nurse1 → 新建患者/查找张建国 → 录入体征 → 查看化验报告
|
||||
Expected: 全流程无报错
|
||||
|
||||
- [ ] **Step 3: 场景 2 — AI 分析**
|
||||
|
||||
进入张建国化验报告 → 点击 AI 解读(或展示预置结果)
|
||||
Expected: AI 输出正常或截图备用
|
||||
|
||||
- [ ] **Step 4: 场景 3 — 医生审批**
|
||||
|
||||
登录 doctor1 → 查看 AI 建议 → 同意 → 查看随访任务
|
||||
Expected: 随访任务自动生成
|
||||
|
||||
- [ ] **Step 5: 场景 4 — 小程序**
|
||||
|
||||
打开小程序(开发者工具)→ 查看消息/随访 → 填写问卷 → 查看趋势
|
||||
Expected: 页面正常渲染,数据正确
|
||||
|
||||
- [ ] **Step 6: 场景 5 — 告警**
|
||||
|
||||
小程序录入血压 185/95 → Web nurse1 查看告警 → 确认 → 处理
|
||||
Expected: 告警实时出现,可操作
|
||||
|
||||
- [ ] **Step 7: 场景 6 — 随访**
|
||||
|
||||
登录 health_manager → 查看随访任务 → 执行 → 录入记录
|
||||
Expected: 随访完成,状态更新
|
||||
|
||||
- [ ] **Step 8: 场景 7 — 仪表盘**
|
||||
|
||||
登录 admin → 查看统计仪表盘 → 查看文章 → 查看积分
|
||||
Expected: 数据有意义(非零)
|
||||
|
||||
- [ ] **Step 9: 记录 DRY RUN 结果**
|
||||
|
||||
在 `docs/qa/demo-dry-run-results.md` 中记录每个场景的结果:
|
||||
- ✅ 通过 / ❌ 失败(附具体错误)
|
||||
- 阻塞问题 → 新建 Task 修复
|
||||
- 可跳过场景标注
|
||||
|
||||
- [ ] **Step 10: Commit DRY RUN 报告**
|
||||
|
||||
```bash
|
||||
git add docs/qa/demo-dry-run-results.md
|
||||
git commit -m "docs: V1 Demo DRY RUN 结果报告"
|
||||
```
|
||||
Reference in New Issue
Block a user