Files
HarborForge/docs/proposal-essential-story-migration.md
zhi c2b11248d7 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
2026-03-29 14:32:23 +00:00

10 KiB
Raw Permalink Blame History

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.pymodels/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.pyapi/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 数据库迁移策略

-- 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
    • featurestory/feature
    • improvementstory/improvement
    • refactorstory/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 机制:

RESTRICTED_TYPE_SUBTYPES = {
    ("story", "feature"),
}

目前仅 story/feature 是 restricted。

3.2 变更内容

所有 story/* 组合 标记为 restricted

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 同上
-- 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/rejectedEssential 不可修改

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 重命名ProposeProposalBE-PR-001, BE-PR-002
  2. 数据库 migration — 表名、字段名变更
  3. 新增 Essential Model/SchemaBE-PR-003, BE-PR-004, BE-PR-005
  4. 新增 Essential CRUD APIBE-PR-006
  5. 重构 Accept 逻辑BE-PR-007, BE-PR-008
  6. 收紧 story restrictedBE-PR-009
  7. 清理 feat_task_idBE-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. 表重命名的 downtimeALTER TABLE RENAME 在 MySQL 中是瞬时操作,但需确保无长事务锁表
  2. 权限 key 更新 — 如果有缓存层,需要在迁移后清理缓存
  3. 前后端同步部署 — 建议同时部署前后端,避免前端调旧路由 404
  4. 回滚方案 — 如需回滚反向执行表重命名和字段重命名即可Essential 表作为新增表,回滚时 DROP 即可