From c2b11248d7036b4b99da40656fae04d116e338b4 Mon Sep 17 00:00:00 2001 From: zhi Date: Sun, 29 Mar 2026 14:32:23 +0000 Subject: [PATCH] DOC-002: Proposal/Essential/Story restricted migration guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Document Propose → Proposal naming changes across all layers - Document Proposal Accept semantic change (single task → Essential-based batch) - Document story/* restricted expansion and impact analysis - Document feat_task_id deprecation and backward compatibility strategy - Document migration execution order and rollback plan --- docs/proposal-essential-story-migration.md | 263 +++++++++++++++++++++ plans/TASKLIST.md | 10 +- 2 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 docs/proposal-essential-story-migration.md diff --git a/docs/proposal-essential-story-migration.md b/docs/proposal-essential-story-migration.md new file mode 100644 index 0000000..87f9a7b --- /dev/null +++ b/docs/proposal-essential-story-migration.md @@ -0,0 +1,263 @@ +# HarborForge — Proposal / Essential / Story Restricted 迁移说明 + +> 更新时间:2026-03-29 +> 本文档描述 Propose → Proposal 重命名、Proposal Accept 语义变更、Essential 新增、以及 story/* restricted 的迁移细节与兼容策略。 + +--- + +## 一、Propose → Proposal 命名调整 + +### 1.1 变更范围 + +| 层 | 旧命名 | 新命名 | 涉及文件 | +|---|--------|--------|---------| +| **数据库表** | `proposes` | `proposals`(新表名) | Alembic migration | +| **Model** | `Propose`, `ProposeStatus` | `Proposal`, `ProposalStatus` | `models/propose.py` → `models/proposal.py` | +| **Schema** | `ProposeCreate`, `ProposeUpdate`, `ProposeResponse`, `ProposeStatusEnum`, `ProposeBase` | `ProposalCreate`, `ProposalUpdate`, `ProposalResponse`, `ProposalStatusEnum`, `ProposalBase` | `schemas/schemas.py` | +| **Router** | `proposes.py`, 路由前缀 `/proposes` | `proposals.py`, 路由前缀 `/proposals` | `api/routers/proposes.py` → `api/routers/proposals.py` | +| **Helper 函数** | `_find_propose()`, `_serialize_propose()`, `_generate_propose_code()`, `_can_edit_propose()` | `_find_proposal()`, `_serialize_proposal()`, `_generate_proposal_code()`, `_can_edit_proposal()` | 同上 router 文件 | +| **Code 字段** | `propose_code` | `proposal_code` | Model、Schema、序列化逻辑 | +| **Activity log** | `entity_type="propose"` | `entity_type="proposal"` | `log_activity()` 调用处 | +| **权限 key** | `propose.accept`, `propose.reject`, `propose.reopen` | `proposal.accept`, `proposal.reject`, `proposal.reopen` | `check_permission()` 调用处、RBAC 数据 | +| **前端** | 页面标题、菜单、API 调用中的 `propose` 文案 | 统一为 `proposal` | 前端组件、路由、API 调用 | +| **CLI** | `hf propose`, `hf propose-create`, `hf propose-accept` 等 | `hf proposal`, `hf proposal create`, `hf proposal accept` 等 | CLI 命令注册与 help 文案 | + +### 1.2 数据库迁移策略 + +```sql +-- Alembic migration +ALTER TABLE proposes RENAME TO proposals; +ALTER TABLE proposals CHANGE propose_code proposal_code VARCHAR(64); +``` + +- `proposal_code` 的已有编码值(如 `HF:P00001`)不变,仅字段名变更 +- 所有外键引用(如 `proposals.id`)保持不变(表内 PK 不变) + +### 1.3 API 兼容策略 + +**方案:直接切换,不保留旧路由** + +理由: +- HarborForge 目前为内部系统,无外部消费者 +- CLI 和前端同步更新,不存在旧客户端兼容问题 +- 保留兼容别名会增加长期维护负担 + +具体做法: +1. 后端路由从 `/projects/{pid}/proposes` 切换为 `/projects/{pid}/proposals` +2. 前端所有 API 调用同步更新 +3. CLI 命令同步更新 +4. **不保留** `/proposes` 旧路由 + +> 如果未来需要面向外部提供 API,应在 API 版本化层面处理兼容,而非在路由别名层面。 + +--- + +## 二、Proposal Accept 语义变化 + +### 2.1 旧行为 + +``` +Proposal (open) ──accept──▶ Proposal (accepted) + │ + ▼ + 单个 story/feature Task + feat_task_id 回填到 Proposal +``` + +- Accept 时自动创建 **1 个** `story/feature` task +- `feat_task_id` 字段记录生成的 task ID + +### 2.2 新行为 + +``` +Proposal (open) + ├── Essential (feature) ──┐ + ├── Essential (improvement) ─┤── accept ──▶ Proposal (accepted) + └── Essential (refactor) ──┘ │ + ▼ + story/feature Task ← from Essential(feature) + story/improvement Task ← from Essential(improvement) + story/refactor Task ← from Essential(refactor) + 全部落入选定 Milestone +``` + +- Proposal 下维护多个 **Essential**(可落地核心条目) +- Accept 时,遍历该 Proposal 下全部 Essential +- 按 Essential.type 映射生成对应 `story/*` task: + - `feature` → `story/feature` + - `improvement` → `story/improvement` + - `refactor` → `story/refactor` +- 生成目标 Milestone 在 Accept 时明确选择(与旧行为一致) +- **批量创建在同一个数据库事务中完成** + +### 2.3 Accept 前置条件变更 + +| 条件 | 旧 | 新 | +|------|----|----| +| 操作者权限 | `propose.accept` | `proposal.accept` | +| 目标 Milestone | 必须选择,状态 `open` | 同上 | +| Essential 数量 | 无此概念 | **必须至少 1 个 Essential**,否则 Accept 报错 | + +### 2.4 Accept 返回值变更 + +- 旧:返回 Proposal 详情(含 `feat_task_id`) +- 新:返回 Proposal 详情 + 嵌套的 Essential 列表 + 每个 Essential 生成的 task 信息 + +--- + +## 三、story/* 改为 Restricted 的影响面 + +### 3.1 当前状态 + +后端 `tasks.py` 中已有 restricted 机制: + +```python +RESTRICTED_TYPE_SUBTYPES = { + ("story", "feature"), +} +``` + +目前仅 `story/feature` 是 restricted。 + +### 3.2 变更内容 + +将 **所有 `story/*` 组合** 标记为 restricted: + +```python +RESTRICTED_TYPE_SUBTYPES = { + ("story", "feature"), + ("story", "improvement"), + ("story", "refactor"), +} +``` + +### 3.3 影响面 + +| 入口 | 影响 | 处理 | +|------|------|------| +| **通用 Task Create API** (`POST /projects/{pid}/milestones/{mid}/tasks`) | 无法创建任何 `story/*` task | 已有 `_validate_task_type_subtype()` 校验,扩展 restricted 集合即可 | +| **Proposal Accept** | 内部受控入口,`allow_restricted=True` | 保留,唯一合法创建 `story/*` 的途径 | +| **前端 Task 创建表单** | `story` 类型从选择器中移除或标为不可选 | 前端调整 | +| **CLI `hf task create`** | 阻止 `--type story` | CLI 侧校验或依赖后端 403 | +| **CSV 导入**(如有) | 阻止 `story/*` 行 | 导入逻辑需加校验 | +| **Milestone 下 Task Create** (`milestones.py` 路由) | 已有 `story/feature` 限制逻辑 | 扩展为全部 `story/*` | + +### 3.4 已有数据兼容 + +- 历史上通过旧 Accept 创建的 `story/feature` task **保持不变** +- 不需要迁移或修改已有 story task +- restricted 仅影响 **新建**,不影响已有记录的读取/更新/状态流转 + +--- + +## 四、旧数据与旧接口兼容策略 + +### 4.1 `feat_task_id` 字段 + +| 处理方式 | 说明 | +|---------|------| +| **保留字段,标记为 deprecated** | 数据库列保留,避免破坏已有数据 | +| **只读** | API 继续返回 `feat_task_id`(已有 Proposal 的值不丢失) | +| **新 Proposal 不再写入** | 新的 Accept 流程不再设置 `feat_task_id` | +| **新增追踪方式** | 通过 Essential → Task 的关联关系(`essential_id` 或关联表)替代 | +| **未来清理** | 等所有旧 Proposal 迁移完毕后,可选择 DROP 该列 | + +### 4.2 旧 Proposal 数据读取 + +- 旧的 `accepted` 状态 Proposal 仍有 `feat_task_id`,前端继续支持 "View Generated Task" 跳转 +- 旧 Proposal 没有 Essential,详情页 Essential 区域显示空列表 +- 不需要为旧 Proposal 补建 Essential 记录 + +### 4.3 Proposal Code 格式 + +- 已有编码如 `HF:P00001` 保持不变 +- 新 Proposal 继续使用相同格式 +- 字段名从 `propose_code` 变为 `proposal_code`,但值的格式不变 + +### 4.4 权限 Key 迁移 + +| 旧 | 新 | 迁移方式 | +|----|----|----| +| `propose.accept` | `proposal.accept` | 数据库中更新 RBAC permission 记录 | +| `propose.reject` | `proposal.reject` | 同上 | +| `propose.reopen` | `proposal.reopen` | 同上 | + +```sql +-- RBAC permission key migration +UPDATE permissions SET key = REPLACE(key, 'propose.', 'proposal.') WHERE key LIKE 'propose.%'; +``` + +> 如果权限 key 存储在 code 中而非数据库,则只需修改代码常量。 + +### 4.5 Activity Log 兼容 + +- 旧 activity log 中 `entity_type="propose"` 的记录保留不动 +- 前端展示时做兼容映射:`propose` → 显示为 "Proposal" +- 新 activity log 统一使用 `entity_type="proposal"` + +--- + +## 五、Essential 新增概要 + +### 5.1 数据模型 + +``` +Essential +├── id (PK) +├── essential_code (UNIQUE) — 格式参考 ProjectCode / TaskCode +├── proposal_id (FK → proposals.id) +├── type (Enum: feature, improvement, refactor) +├── title +├── description (nullable) +├── created_by_id (FK → users.id, nullable) +├── created_at +└── updated_at +``` + +### 5.2 EssentialCode 编码 + +- 格式:`{proposal_code}:E{i:05x}` +- 示例:`HF:P00001:E00001` +- 每个 Proposal 独立递增 + +### 5.3 Essential 生命周期 + +1. Proposal `open` 状态下可创建/编辑/删除 Essential +2. Proposal Accept 时,全部 Essential 用于生成 `story/*` task +3. Proposal 进入 `accepted`/`rejected` 后,Essential 不可修改 + +### 5.4 API 端点 + +| 操作 | 方法 | 路径 | +|------|------|------| +| 列出 | GET | `/projects/{pid}/proposals/{proposal_id}/essentials` | +| 创建 | POST | `/projects/{pid}/proposals/{proposal_id}/essentials` | +| 详情 | GET | `/projects/{pid}/proposals/{proposal_id}/essentials/{id}` | +| 编辑 | PATCH | `/projects/{pid}/proposals/{proposal_id}/essentials/{id}` | +| 删除 | DELETE | `/projects/{pid}/proposals/{proposal_id}/essentials/{id}` | + +--- + +## 六、迁移执行顺序 + +推荐按以下顺序执行,确保每一步可独立验证: + +1. **后端 Model/Schema 重命名** — `Propose` → `Proposal`(BE-PR-001, BE-PR-002) +2. **数据库 migration** — 表名、字段名变更 +3. **新增 Essential Model/Schema** — (BE-PR-003, BE-PR-004, BE-PR-005) +4. **新增 Essential CRUD API** — (BE-PR-006) +5. **重构 Accept 逻辑** — (BE-PR-007, BE-PR-008) +6. **收紧 story restricted** — (BE-PR-009) +7. **清理 feat_task_id** — (BE-PR-010) +8. **前端同步更新** — (FE-PR-001 ~ FE-PR-005) +9. **CLI 同步更新** — (CLI-PR-001 ~ CLI-PR-004) +10. **测试补全** — (BE-PR-011, TEST-BE-PR-001, TEST-FE-PR-001, TEST-CLI-PR-001) + +--- + +## 七、风险与注意事项 + +1. **表重命名的 downtime** — `ALTER TABLE RENAME` 在 MySQL 中是瞬时操作,但需确保无长事务锁表 +2. **权限 key 更新** — 如果有缓存层,需要在迁移后清理缓存 +3. **前后端同步部署** — 建议同时部署前后端,避免前端调旧路由 404 +4. **回滚方案** — 如需回滚,反向执行表重命名和字段重命名即可;Essential 表作为新增表,回滚时 DROP 即可 diff --git a/plans/TASKLIST.md b/plans/TASKLIST.md index a344d65..aa5437b 100644 --- a/plans/TASKLIST.md +++ b/plans/TASKLIST.md @@ -16,11 +16,11 @@ - [x] 明确两条线的交叉点仅限 `event_data` / Agent 调度引用层 - [x] 将"必须本波完成"和"仅设计保留"区分写清楚 -- [ ] DOC-002:整理 Proposal / Essential / Story restricted 的迁移说明(root docs only) - - [ ] 说明 `Propose -> Proposal` 的命名调整 - - [ ] 说明 `Proposal Accept` 语义变化 - - [ ] 说明 `story/*` 改为 restricted 的影响面 - - [ ] 说明旧数据和旧接口的兼容策略 +- [x] DOC-002:整理 Proposal / Essential / Story restricted 的迁移说明(root docs only) + - [x] 说明 `Propose -> Proposal` 的命名调整 + - [x] 说明 `Proposal Accept` 语义变化 + - [x] 说明 `story/*` 改为 restricted 的影响面 + - [x] 说明旧数据和旧接口的兼容策略 - [ ] DOC-003:整理 Calendar 验收清单(root docs only) - [ ] 列出后端验收项