DOC-002: Proposal/Essential/Story restricted migration guide

- 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
This commit is contained in:
zhi
2026-03-29 14:32:23 +00:00
parent d6ed523731
commit c2b11248d7
2 changed files with 268 additions and 5 deletions

View File

@@ -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 即可

View File

@@ -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)
- [ ] 列出后端验收项