# MVP v3.0 开发计划（TDD 驱动）

本文是 v3.0 的**工程化开发计划**，强调“先写测试，再写实现”（TDD），并将每个里程碑拆成**可独立验收**的小闭环。

输入依据：
- 路线图：`specs/mvp/mvp_roadmap_v2.md`
- v3.0 设计：`specs/mvp/v3.0/v3.0_design.md`
- v3.0 API：`specs/mvp/v3.0/v3.0_api.md`
- v3.0 验收：`specs/mvp/v3.0/v3.0_acceptance.md`
- 现状基线：v2.5（Task queue + User mgmt + Stateless ray pool + 单镜像节点守护）

v3.0 已确认约束：
- 允许用户数据集路径：`/private/users/<me>/datasets/...`
- 允许用户本地模型路径：`/private/users/<me>/models/...`
- **不允许执行用户自定义代码**（不注入 user code 到 PYTHONPATH；`code_path` 仍只允许 `/private/common/...`）
- SFTPGo 先用 **password** 方案（方案 A：API 联动创建/管理 SFTPGo 用户）
- jobs retention：**3 天移入回收站（trash/jobs），再 7 天永久删除**；不提供 keep/延长保留标记
- janitor：**API server 内置后台线程**；删除/移动采用**文件系统直接操作**（不依赖 SFTPGo API）

---

## 0. TDD 规范（所有功能都遵循）

### 0.1 测试分层

1) **单元测试（fast）**
- 纯 Python 逻辑：路径策略、SFTPGo client、retention 计算、文件移动/删除策略（用临时目录）。
- 不依赖真实 Ray、不依赖 docker、不依赖网络。

2) **组件测试（中等）**
- FastAPI 路由（含 WebUI 路由）：`fastapi.testclient.TestClient`
- mock/stub SFTPGo client 与 ray client

3) **端到端（慢）**
- 在 `argus@h1` 通过 docker compose + scripts：
  - Ray 集群自动起来（head+2 worker）
  - SFTPGo 服务可用
  - 上传数据 → 提交训练 → 下载产物 → jobs 回收站/清理

### 0.2 代码与测试约定
- 测试目录：`src/mvp/py/tests/`
- 新功能必须先补齐测试用例，并让其在未实现时失败（红）
- 最小实现让测试变绿（绿）
- 再做重构（重构）
- 覆盖率：继续沿用当前阈值（>= 90%）

---

## 1. 里程碑拆分（v3.0 = 5 个可验证闭环）

### M1：TaskSpec 路径策略升级（允许 user datasets/models；code_path 仍仅 common）

**目标**
- API submit 时的路径校验从 v2.5 的 “仅 `/private/common/`” 升级为：
  - `train_file` / `val_file`：允许 `/private/common/...` 与 `/private/users/<me>/...`
  - 本地模型路径：允许 `/private/users/<me>/models/...`（不改变 YAML 结构，见实现建议）
  - `code_path`：仍仅允许 `/private/common/...`
- 阻止越权路径（`/private/users/other/...`）与非 `/private/...` 路径。

**实现建议（不扩展 TaskSpec）**
- `model_id` 字段保持不变：
  - 若 `model_id` 以 `/private/` 开头 → 视作本地模型路径
  - 否则视作 HuggingFace repo id（如 `Qwen/...`）

**TDD 用例（先写测试）**
- 单测：
  - `test_paths_allow_common_and_own_user_prefix()`
  - `test_paths_reject_other_user_prefix()`
  - `test_model_id_local_path_allowed_only_under_users_models()`
  - `test_code_path_still_common_only()`
- API 测试：
  - `test_submit_accepts_user_datasets_paths()`
  - `test_submit_rejects_cross_user_paths_404_or_400()`（按约定返回 400/403）

**验收点**
- `v3.0_acceptance.md` 的 D 类安全隔离用例可由 API 测试覆盖。

---

### M2：SFTPGo 集成（方案 A：用户联动创建 + password）

**目标**
- 引入 `data management (SFTPGo)`：
  - admin 创建用户时联动创建 SFTPGo 用户（home=/private/users/<user_id>，chroot）
  - password 模式：生成一次性密码（reset/create）并返回给 admin（明文只返回一次）
- 提供用户自助信息：
  - `GET /api/v2/me` 返回 SFTP 连接信息、目录约定、retention 提示。

**实现建议**
- 新增 `SFTPGoAdminClient`（同步调用）：
  - 通过 `urllib` 或 `httpx`（建议 `urllib`，减少依赖；禁止 hard-code requests 使用）
  - 支持：create user / disable user / reset password（最小集合）
- API server 启动时校验配置（enabled 时必须具备 admin 密码 env）。
- 同步创建用户目录结构（文件系统）：
  - `/private/users/<u>/{datasets,models,code,jobs,trash/jobs}`（最小必需）

**TDD 用例（先写测试）**
- 单测：
  - `test_sftpgo_client_builds_correct_requests()`（不发真实网络；mock urlopen）
  - `test_user_dirs_created_on_user_create()`（tmp dir 断言目录存在）
- API 测试：
  - `test_create_user_calls_sftpgo_client()`（stub client，断言调用参数）
  - `test_me_returns_sftp_info_and_paths()`（含 trash/jobs 与 TTL 字段）

**验收点**
- `v3.0_acceptance.md` 的 A 类（用户/凭据）与 B 类（上传闭环前置）覆盖。

---

### M3：WebUI（最小可用，多页面 + 侧边栏）

**目标**
- WebUI 由 API server 托管（同源，无额外 CORS）：
  - `/ui/login`：token 粘贴登录（localStorage）
  - `/ui/tasks`：任务列表 + 过滤（最小）
  - `/ui/tasks/new`：YAML 提交（优先）+（可选）表单生成 YAML
  - `/ui/tasks/{task_id}`：详情页
  - `/ui/tasks/{task_id}/logs`：日志 tail + 可选自动刷新
  - `/ui/data`：SFTP 信息 + 目录/retention 提示
  - （可选）`/ui/admin/users`：管理员用户管理（若时间允许，强烈建议）

**实现建议**
- 先不引入 Node 构建：
  - HTML 模板可用最简单的字符串拼接或 Jinja2（若引入 jinja2，则补齐依赖与测试）
  - 页面通过 fetch 调用 `/api/v2/...`，并复用 token header

**TDD 用例（先写测试）**
- 组件测试（TestClient）：
  - `test_ui_routes_render_200()`
  - `test_ui_contains_sidebar_links()`（简单断言文本包含导航链接）
  - `test_ui_tasks_detail_shows_ids()`（包含 task_id、state、ray_submission_id）

**验收点**
- WebUI 能完成：登录→创建任务→查看任务→查看日志→看到 data 页提示。

---

### M4：Jobs Retention janitor（3 天移入 trash，7 天后 purge）

**目标**
- API server 内置 janitor 后台线程：
  - 周期性扫描 DB 中 terminal tasks
  - 到期后执行：
    - move：`/private/users/<u>/jobs/<sid>` → `/private/users/<u>/trash/jobs/<sid>`
    - purge：递归删除 `/private/users/<u>/trash/jobs/<sid>`
  - 全程严格 path 校验，禁止越界删除
  - 清理操作记录到 DB events（审计）

**实现建议（数据与状态）**
- 需要稳定的时间锚点与幂等：
  - 使用 attempts.end_time 作为 job 结束时间（latest attempt）
  - 在 tasks 表新增字段（或新表）记录：
    - `trashed_at`（首次成功 move 时间）
    - `purged_at`（成功删除时间）
    - `trash_path`（可选）
  - 幂等：重复运行不会报错（目录不存在视为已处理）

**TDD 用例（先写测试）**
- 单测（用 tmpdir 构造 jobs/trash 目录）：
  - `test_janitor_moves_job_to_trash_after_threshold()`
  - `test_janitor_purges_trash_after_threshold()`
  - `test_janitor_never_touches_models_or_datasets()`
  - `test_janitor_path_escape_rejected()`（恶意 path 不可删）
- API/组件测试：
  - `test_me_includes_retention_fields()`（jobs_trash_after_days/jobs_purge_after_days）

**验收点**
- `v3.0_acceptance.md` 的 C2 用例可按“把阈值调小到分钟级”完成验证。

---

### M5：端到端（h1）— SFTP 上传→训练→产物下载→回收站/清理

**目标**
- 在 `argus@h1` 落一个一键脚本（或手册）跑通：
  1) `docker compose up -d` 拉起 Ray（head+2 worker）+ SFTPGo
  2) admin 创建用户 alice（联动创建 SFTPGo 用户 + password）
  3) alice 通过 SFTP 上传：
     - 数据集到 `/private/users/alice/datasets/...`
     - （可选）本地模型到 `/private/users/alice/models/...`
  4) alice 通过 API/WebUI 提交任务引用上述路径
  5) 任务成功后：
     - 从 `jobs/<sid>` 下载 logs/checkpoints
     - 把权重移动到 `models/`，验证不会被清理
  6) 把 retention 配置调小，验证 jobs→trash→purge

**交付建议**
- 新增脚本（命名示例）：
  - `scripts/run_all_v30_api.sh`
  - `scripts/run_e2e_v30_cases.sh`
- 新增 `docker-compose.yaml` 中的 `sftpgo` service（或 `docker-compose.v30.yaml` 叠加文件）

**验收点**
- `v3.0_acceptance.md` 全部 MUST 用例通过。

---

## 2. 风险与测试关注点

1) **权限与路径逃逸**
- path policy 必须覆盖：train/val/model_id(local)/output dirs（jobs/trash）
- 所有删除/移动必须做 prefix 校验

2) **并发与竞态**
- janitor 只处理 terminal tasks，避免清理正在写入的目录
- move 使用同文件系统 `os.replace`（原子）

3) **SFTPGo 可用性**
- SFTPGo 不在线不应影响训练与 API 核心功能（除了用户创建联动）
- janitor 不依赖 SFTPGo（文件系统直连）

---

## 3. 交付清单（代码/配置/脚本/文档）

### 3.1 代码
- Path policy（v3.0）
- SFTPGoAdminClient + user create/disable/reset password 联动
- `/api/v2/me` 扩展（SFTP/目录/retention）
- WebUI 路由与静态资源
- janitor（trash+purge）后台线程 + DB 记录

### 3.2 配置
- `configs/dev.yaml` 增加 `data.sftpgo`、`data.retention` 段（详见设计文档）

### 3.3 scripts / compose
- compose 增加 `sftpgo`（或新增 overlay compose 文件）
- v3.0 e2e 脚本（上传/下载/清理验证）

### 3.4 文档
- 更新 `specs/mvp/v3.0/*` 与 `src/mvp/README.md`（运行方式、路径约定、SFTP 操作、retention 解释）

