21 KiB
HarborForge 需求草案:Milestone 状态机与 Propose 流程
状态:草案 更新时间:2026-03-16 目的:先把业务想法固定下来,后续再拆数据库、API、前端和测试实现。
0. 开发状态
2026-03-16 23:49 UTC(第 1 轮:基线盘点 + 自动开发调度)
当前进度:已启动按小时推进的开发节奏,先完成一轮代码基线盘点,方便后续按模块逐步落地。
本轮已确认:
- 后端 milestone 旧状态枚举仍在使用:
open / pending / deferred / progressing / closed- 位置:
HarborForge.Backend/app/models/milestone.py
- 位置:
- 后端 task 旧状态枚举仍在使用:
open / pending / progressing / closed- 位置:
HarborForge.Backend/app/models/task.py、HarborForge.Backend/app/models/models.py、HarborForge.Backend/app/schemas/schemas.py
- 位置:
- 后端与前端都还有多处写死
progressing/pending/deferred- 典型位置:
- 后端:
app/api/routers/milestones.py、app/api/routers/tasks.py、cli.py - 前端:
src/types/index.ts、src/components/MilestoneFormModal.tsx、src/pages/MilestoneDetailPage.tsx、src/pages/TaskDetailPage.tsx
- 后端:
- 典型位置:
task_type = task仍真实存在,且当前主要表现为subtype = defect- 后端:
HarborForge.Backend/app/api/routers/tasks.py的TASK_SUBTYPE_MAP - 前端:
HarborForge.Frontend/src/components/CreateTaskModal.tsx、HarborForge.Frontend/src/pages/CreateTaskPage.tsx
- 后端:
当前判断:
- 下一步最合适的是先做 P0/P1 之间的“状态枚举与迁移骨架”,否则后续 milestone / task / propose 的接口实现会反复返工。
- 具体优先级建议:
- 先统一后端 milestone/task 新旧状态枚举与 schema
- 再补 migration / 兼容映射
- 然后推进动作接口、权限、前端按钮与页面
阻塞项:
- 暂无硬阻塞;但
closed/completed的终态与 task reopen 已写入需求,后续实现时要一次性考虑兼容,避免先做死状态流转。
2026-03-17 00:00 UTC(第 2 轮:后端状态枚举全量替换)
本轮做了什么:
- 将 milestone 和 task 的后端状态枚举全部替换为新值
- Milestone:
open/pending/deferred/progressing/closed→open/freeze/undergoing/completed/closed - Task:
open/pending/progressing/closed→open/pending/undergoing/completed/closed - 新增
MilestoneStatusEnum到 schemas.py,milestone 的 create/update/response 现在使用类型化枚举而非裸字符串 - 新增
started_at字段到 Milestone model 和 MilestoneResponse - 更新所有 router(milestones.py, tasks.py, misc.py)中
progressing→undergoing引用 - 更新 task transition 逻辑支持
completed状态自动记录finished_on - 更新 CLI status icon 映射和 choices 参数
改了哪些关键文件:
HarborForge.Backend/app/models/milestone.py— enum + started_atHarborForge.Backend/app/models/task.py— enumHarborForge.Backend/app/models/models.py— enumHarborForge.Backend/app/schemas/schemas.py— MilestoneStatusEnum + TaskStatusEnum + started_atHarborForge.Backend/app/api/routers/milestones.py— progressing→undergoingHarborForge.Backend/app/api/routers/tasks.py— progressing→undergoing, completed handlingHarborForge.Backend/app/api/routers/misc.py— progressing→undergoing (3处)HarborForge.Backend/cli.py— status icons + choices
验证结果:
- 全部 8 个文件 Python AST 语法检查通过
- 后端无 sqlalchemy 环境无法做运行时验证,但枚举定义和引用已全部对齐
- grep 确认后端代码中不再有
progressing/deferred残留
当前阻塞/风险:
- 数据库迁移脚本尚未编写(MySQL enum 列需要 ALTER TABLE 才能接受新值)
- 前端仍使用旧状态值,需要同步更新
- 没有本地 DB/Docker 环境做集成验证
下一轮最建议继续做什么:
- P1.5 数据库迁移骨架:编写 Alembic 或手写 SQL 迁移脚本,处理 MySQL enum 列的 ALTER 和旧数据映射
- 或者先做前端 TypeScript 类型定义更新(影响面小、独立性强)
1. 背景
当前希望把 milestone 的推进过程做成一个更严格的状态机,并把 feature story 的进入方式从“直接创建 task”改为“先提 propose,再 accept 进入 milestone”。
核心目标:
- milestone 有明确生命周期
- feature 进入 milestone 要受控
- release 节点要能约束 freeze / complete
- 依赖关系在开始前做严格检查
2. Milestone 字段调整
Milestone 的 status 使用新的枚举集合,替代当前代码库中的旧枚举。
2.1 目标枚举
openfreezeundergoingcompletedclosed
2.2 替代当前代码中的旧枚举
当前代码里 milestone status 旧枚举为:
openpendingdeferredprogressingclosed
后续应整体替换为新的 milestone 状态集合,不再使用:
pendingdeferredprogressing
另增字段:
started_at- milestone 进入
undergoing时自动记录开始时间
- milestone 进入
默认:
- 新建 milestone 时,
status = open
3. Milestone 状态定义
3.1 open
含义:
- milestone 刚创建,仍可接收 feature 范围内的内容
页面按钮:
freezeclose
约束:
- milestone 下的
task / support / meeting在此阶段都只能处于锁定状态,不允许进入真正开始执行的状态 - 当前可认为它们只能停留在
pending - milestone 基本信息仍可编辑
3.2 freeze
含义:
- milestone 进入冻结,需求范围不再继续扩张,准备开始执行
进入条件:
- 该 milestone 有且仅有一个
maintenance task - 且这个 maintenance task 的
subtype = release - 如果不满足,不能进入
freeze
页面按钮:
startclose
约束:
- freeze 之后不再允许做范围调整
- 如果 freeze 之后才发现要调整范围,只能放到未来的 milestone,不提供
unfreeze - milestone 不能再新增
subtype = feature的story task - 已存在的
feature story task不能再编辑核心字段- 例如:
title、description、owner等
- 例如:
- 但允许继续做附属操作
- 例如:新增 comment
- milestone 下的
task / support / meeting仍保持锁定,不允许进入执行中状态 - 当前可认为仍只能停留在
pending
3.3 undergoing
含义:
- milestone 正式开始执行
进入方式:
- 用户点击
start
进入前检查:
- milestone 自身的前置依赖必须全部完成
- 其前置依赖中涉及的 milestone / task / 其他依赖对象,如果尚未完成,则不能开始
进入后效果:
- milestone
status = undergoing - 自动记录
started_at - detail 页面只显示:
close - milestone 下的
task / support / meeting才允许进入可执行状态 - 也就是说,只有在 milestone 为
undergoing之后,这些对象才可以从pending往后推进
3.4 completed
含义:
- milestone 正常完成
进入方式:
- 当该 milestone 下唯一的
release maintenance task完成后,milestone 自动进入completed
约束:
completed是终态- milestone 一旦进入
completed,状态不再允许变更
备注:
- “task 完成”所对应的 task 状态枚举,后续补充
3.5 closed
含义:
- 废弃该 milestone,而不是正常完成
适用场景:
- milestone 不再继续推进
- 即使已经开始,也可以选择废弃
页面入口:
open/freeze/undergoing状态下都可以通过close进入closed
进入 closed 后的处理原则:
- 如果 milestone 尚未开始,则直接废弃
- 如果 milestone 已经开始:
- 已完成的
feature task可以被废弃 - 或者移交到其他
open状态的 milestone
- 已完成的
- 所有依赖该 milestone 的对象,都需要修改依赖关系,避免继续依赖一个已废弃 milestone
约束:
closed视为终态- 当前不考虑 reopen 原 milestone
- milestone 页面不提供状态动作按钮
4. Milestone 页面按钮草案
所有按钮都按权限可见。
对于需要前置校验的按钮,推荐交互:
- 按钮可见但禁用
- 并给出提示,说明当前为什么不能点击
4.1 各状态按钮
-
openfreezeclose
-
freezestartclose
-
undergoingclose
-
completed- 不显示状态动作按钮
-
closed- 不显示状态动作按钮
4.2 按钮触发条件
freeze
- 仅
open状态显示 - 点击前检查:
- 当前 milestone 下有且仅有一个
maintenance/release task
- 当前 milestone 下有且仅有一个
- 不满足时不可点击,并提示原因
start
- 仅
freeze状态显示 - 点击前检查:
- 当前 milestone 的前置依赖全部完成
- 满足后:
- milestone 进入
undergoing - 自动记录
started_at
- milestone 进入
close
open/freeze/undergoing状态显示- 需要有对应权限
- 如果 milestone 已经是
undergoing,建议弹确认说明:- 已完成的 feature task 需要废弃或移交
- 依赖该 milestone 的对象需要调整依赖
4.3 Milestone 动作权限
将以下三类动作设计为独立权限,由 role 控制:
freeze milestonestart milestoneclose milestone
规则:
- 拥有对应权限的 role 才可以执行对应动作
- 三者互相独立,不合并
4.4 特别说明
- milestone 不提供
unfreeze - 范围调整只能发生在
freeze之前 - 如果 freeze 之后才发现需要调整范围,只能放到未来的 milestone
- milestone 不提供手动
complete按钮completed由唯一的release maintenance task完成后自动触发
5. 依赖规则
4.1 开始前依赖检查
以下对象在进入“开始/执行中”状态前,都要检查依赖是否已完成:
- milestone
- task
- support
- meeting
- 其他有依赖关系的对象
规则:
- 如果任一前置依赖未完成,则当前对象不能开始
- 不需要在依赖完成时批量自动推进下游状态
- 只在“尝试开始”这一刻做校验即可
4.2 milestone close 对依赖的影响
当 milestone 被 closed:
- 所有依赖它的对象都必须调整依赖关系
- 调整后,这些对象未来在开始时仍然通过“开始前依赖检查”判断是否可开始
也就是说:
- 不做统一的自动状态更新
- 只要求依赖配置保持合法
5. Task 创建限制
以下两类 task 不能通过通用的 create task 页面直接创建:
feature story taskrelease maintenance task
设计意图:
feature story task应来自propose -> acceptrelease maintenance task应通过更受控的 milestone/release 流程创建
6. Propose 新模型
新增 propose 表。
6.1 propose 与 project 的关系
propose通过外键连接到project
6.2 propose code
格式:
{proj_code}:P{i:05x}
说明:
i由每个project独立维护递增序号- 即每个 project 都有自己的 propose 计数器
6.3 propose 状态枚举
当前采用以下状态:
openacceptedrejected
明确:
- propose 不需要
closed
6.4 propose 基础字段(当前已明确部分)
至少需要:
project_idpropose_codetitledescriptionstatuscreated_by_idcreated_atupdated_at
6.5 feat_task_id 字段
新增字段:
feat_task_id- 类型:
string - 可选
规则:
- 在 accept 生成
feature story task后自动填写 - 不允许手动修改
- 前端页面应显示该字段
7. Propose 页面草案
7.1 页面按钮
前端页面按钮规则采用当前草案:
-
openacceptreject
-
accepted- 不显示状态动作按钮
- 可显示跳转入口:
View Generated Task
-
rejectedreopen
所有按钮都按权限可见。
7.2 页面显示字段
前端 detail 页面至少应显示:
propose_codetitledescriptionstatusfeat_task_idcreated_by_idcreated_atupdated_at
8. Propose 状态流转与动作规则
8.1 允许的状态流转
open -> acceptedopen -> rejectedrejected -> open
当前不考虑:
accepted -> openaccepted -> rejected- 任何
closed相关流转(因为 propose 不设closed)
8.2 accept
前置条件:
- propose 当前必须是
open - 操作者有
accept propose权限 - 用户必须从下拉框中选择一个目标 milestone
- 目标 milestone 必须属于同一个 project
- 目标 milestone 必须是
open
accept 后效果:
- propose.status =
accepted - 自动在所选 milestone 下创建一个
feature story task - 新 task 的创建者与 propose 创建者保持一致
- 新 task 默认状态建议为
pending - 自动填写
feat_task_id
字段继承:
titledescription
8.3 reject
前置条件:
- propose 当前必须是
open - 操作者有
reject propose权限
reject 后效果:
- propose.status =
rejected - 建议要求填写 reject comment / reason,便于追踪原因
8.4 reopen
前置条件:
- propose 当前必须是
rejected - 操作者有
reopen rejected propose权限
reopen 后效果:
- 不创建新 propose
- 复用当前 propose
- propose.status 回到
open - 系统需要保留一条 reopen 记录
9. Propose 权限与编辑规则
9.1 独立权限
建议将以下动作设计为独立权限:
accept proposereject proposereopen rejected propose
9.2 编辑规则
open- creator 可编辑
title、description - admin 可编辑
- 有特定管理权限的 role 可编辑
- creator 可编辑
accepted- 主体不可编辑
rejected- 主体不可编辑
备注:
- 主体不可编辑不代表不能追加 comment / activity 记录
10. 当前推荐的业务流程
10.1 feature 进入 milestone
推荐流程:
- 用户先在 project 下创建
propose - 负责人在 propose detail 页面审阅
- 若接受,则选择目标 milestone 并
accept - 系统自动生成
feature story task - milestone 处于
open时可继续接纳 feature story - milestone 进入
freeze后,不再接受新的 feature story - milestone 满足条件后
start,进入undergoing - milestone 下唯一
release maintenance task完成后,milestone 自动completed
10.2 废弃 milestone
- 用户点击
close - milestone 进入
closed - 处理已完成 feature task:
- 废弃
- 或移交到其他
openmilestone
- 修正所有依赖该 milestone 的对象依赖配置
11. Task 状态枚举调整
Task 的 status 也采用新的枚举集合。
11.1 目标枚举
openpendingundergoingcompletedclosed
11.2 替代当前代码中的旧枚举
当前代码里 task status 旧枚举为:
openpendingprogressingclosed
后续应整体替换为新的 task 状态集合,不再使用:
progressing
并新增:
undergoingcompleted
11.3 Task 状态语义
pending- 还不能开始,通常是被 milestone 状态或依赖锁住
open- 可以开始,但尚未真正开工
undergoing- 正在处理
completed- 正常完成
closed- 废弃 / 取消,不是正常完成
11.4 当前认可的 Task 基础状态流转
允许的基础流转:
pending -> openopen -> undergoingundergoing -> completedpending -> closedopen -> closedundergoing -> closed
11.5 各状态流转条件
pending -> open
条件:
- 所有前置依赖都已满足
open -> undergoing
条件:
assignee必须非空- 且只有当前 assignee 可以执行这个开始动作
undergoing -> completed
条件:
- 不额外要求前置条件
- 但 assignee 需要留下 comment
11.6 Reopen 方向
补充决定:
- reopen 时不创建新 task
- 直接复用当前 task
- 但系统需要保留一条 reopen 记录
当前已确认:
- reopen 允许从
closed与completed触发 - reopen 后统一回到
open - reopen 记录采用 comment、activity log 还是专门字段/表,后续再定
当前理解:
completed与closed原本被视为终态- 但 task 将引入受控的 reopen 机制,使其可以返回
open
11.7 Task 状态动作权限
将以下三类动作设计为独立权限,由 role 控制:
close taskreopen closed taskreopen completed task
规则:
- 拥有对应权限的 role 才可以执行对应动作
reopen closed task与reopen completed task分开控制,不合并为一个权限
补充:
undergoing -> completed不走 role 权限控制complete只有当前 assignee 可以执行
11.8 Task 页面按钮可见性
所有按钮都按权限可见。
当前确认的按钮规则:
- 除
closed与completed外,task 页面始终显示close按钮- 也就是:
pending有closeopen有closeundergoing有close
- 也就是:
closed与completed状态显示reopen按钮pending状态显示open按钮open状态显示start按钮undergoing状态显示finish按钮
可理解为:
pending:open+closeopen:start+closeundergoing:finish+closecompleted:reopenclosed:reopen
其中:
finish对应undergoing -> completedstart对应open -> undergoingopen按钮对应pending -> open
11.9 与 Milestone 状态的联动约束
- 当 milestone 为
open/freeze时:- task 原则上只能处于
pending或closed - 不应进入
open / undergoing / completed
- task 原则上只能处于
- 当 milestone 为
undergoing时:- task 才允许从
pending往执行状态推进
- task 才允许从
- 当 milestone 为
completed/closed时:- milestone 下 task 原则上不再继续推进
11.10 新建 Task 的默认状态
当前倾向:
- 新建 task 默认
pending
原因:
- 与 milestone
open / freeze阶段的“锁定”语义一致 - 避免 task 一创建就被误认为可执行
11.11 Assignee 字段与编辑权限规则
Task 需要一个字段:
assignee- 类型:
string - 可选
- 含义:用户 id
当前业务规则:
open 状态
- 当
assignee = null时:- 任何人都可以编辑
- 当
assignee != null时:- 仅当前 assignee 和 admin 可以编辑
undergoing 以及之后的状态
undergoingcompletedclosed
以上状态一律不可编辑。
备注:
- 这里的“不能编辑”指 task 主体内容不可编辑
undergoing -> completed已明确要求 assignee 留下 comment,因此 comment / 记录性动作不等同于主体编辑- 其他 comment 等附属动作的权限边界,后续可继续细化
12. Task 类型清理
当前又发现一个需要清理的点:
- 现在创建 task 时,存在一个
task_type = task的类型 - 且它当前只有一个 subtype:
defect
这在语义上比较别扭,也和其它类型存在重叠/混淆。
12.1 当前决定
后续应移除这个类型:
- 移除
task_type = task
12.2 当前代码确认结果
这个问题在当前代码中已经确认存在,而且前后端都写死了:
前端:
HarborForge.Frontend/src/components/CreateTaskModal.tsxHarborForge.Frontend/src/pages/CreateTaskPage.tsx- 两处都定义了:
task- 其 subtype 只有:
defect
- 当前创建表单默认类型也是
task
后端:
HarborForge.Backend/app/api/routers/tasks.pyTASK_SUBTYPE_MAP中明确存在:'task': {'defect'}
补充:
- 前端类型定义里也仍包含
task - 后端 schema / enum 里也仍包含
task
12.3 本轮只记录,不改代码
当前先把它记录为需求与清理项:
- 暂不实现
- 暂不修改代码
- 后续统一与 task type / subtype 体系一起调整
13. 待补充事项
这些点后续需要进一步定死:
13.1 feature task 在 freeze 后的可编辑字段白名单/黑名单
- 当前只明确:核心字段不可编辑,comment 仍可新增
- 后续要更具体列出:哪些能改,哪些不能改
13.2 release maintenance task 的创建入口
- 当前只明确它不能走通用 create task
- 但具体从哪里创建、谁能创建、何时创建,还需要补
13.3 close 时的迁移细节
- 已完成 feature task 移交到其他
open milestone时,是否保留原历史链接 - task code 是否需要变化
- 依赖链迁移是否允许批量操作
14. 一句话总结
这套方案的本质是:
- 用
propose控制 feature 进入 milestone 的入口 - 用
milestone status控制范围冻结与执行节奏 - 用
release maintenance task作为 milestone 完成的关键闸门 - 用“开始前依赖检查”替代大规模自动状态联动
这样业务会更可控,也更容易避免流程混乱。