fix(docgov): pass dynamic StageKey into document ledger create
Stop hardcoding V0.5_feasibility when creating docgov records; prefer frozen-instance StageKey from TaskRunRequest with main-task fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "研发工作流(rdev)接入:13 阶段 / 60 文档任务"
|
||||
status: in_progress
|
||||
created: 2026-09-17
|
||||
updated: 2026-09-17
|
||||
owner: Cursor
|
||||
branch: fix/docgov-stage-dynamic → feature/rdev-core
|
||||
worktree: "-"
|
||||
mutex: workflow-def
|
||||
---
|
||||
|
||||
# 研发工作流(rdev)接入:13 阶段 / 60 文档任务
|
||||
|
||||
## 目标
|
||||
|
||||
在现有 5 个正式工作流外新增医疗第 4 工作流「研发设计控制流程」(key: `rdev`),完整承载参考项目 13 phase / 60 文档任务的 ISO 13485 DHF 设计控制链,并通过真实 PI 验收;随后完成全工作流任务键命名空间化(P4)。
|
||||
|
||||
## 范围
|
||||
|
||||
- 方案:[07-研发工作流rdev接入-设计与实施计划](../../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md)(P0–P4 五期;§8 四项决策已确认)
|
||||
- P0:docgov Stage 去硬编码 + stage key 透传
|
||||
- P1:rdev.v2.json manifest(13×60 一次到位)+ 60 份 rd_ 前缀模板 + catalog/seed/registry + squad kind 编排 + main-completed precheck + 齐套测试 + stub PG 全链路
|
||||
- P2:前端激活规划/切换/前置 Banner + intent 文档名匹配 + prior docs 注入与血缘
|
||||
- P3:真实 PI Agent E2E(rd_proposal / rd_planning_review / rd_sw_plan 三样本)+ CI 门禁扩四
|
||||
- P4:现有工作流(main/pm/tm + 软件两盘点)任务键 `bd_`/`pm_`/`tm_`(`sw_`)空间化,alias 兼容式改名(D12),独立分支独立验收
|
||||
|
||||
## 非目标
|
||||
|
||||
- 二期增强全部移入 backlog:[2026-09-17-rdev-phase2-backlog](../../backlog/2026-09-17-rdev-phase2-backlog.md)(N/A 不适用页生成、UR/RS 结构化门禁、kind 专用门禁、depends_on 精确血缘、DMR 归档、sw_ 键盘点、complexity regulatory 注入)
|
||||
- 云盘 canonical DHF/DMR 归档(本仓库无云盘模块)
|
||||
- 软件域(non_medical_software)开放 rdev
|
||||
|
||||
## 步骤
|
||||
|
||||
1. [x] 用户确认方案 §8 四项决策(2026-09-17:名称「研发设计控制流程」/ 60 任务一次到位 / N/A 规划期关闭 + backlog / rd_ 前缀并扩展到现有工作流键名空间化)
|
||||
2. [ ] P0 分支 `fix/docgov-stage-dynamic` + PG 回归
|
||||
3. [ ] P1 分支 `feature/rdev-core`(manifest/模板/seed/编排/precheck/齐套测试)
|
||||
4. [ ] P2 分支 `feature/rdev-web-governance`(前端 + intent + 血缘)— 骨架已定,本期不实现
|
||||
5. [ ] P3 真实 PI 验收 + 报告归档 + FEATURE-MATRIX 更新 — 骨架已定,本期不实现
|
||||
6. [ ] P4 分支 `feature/workflow-key-namespace`(D12 alias 兼容式改名 + 双路径回归)— 骨架已定,本期不实现
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `go vet ./...` && `go test -short ./...` PASS
|
||||
- [ ] PG 集成:rdev 实例 planning→live→13 阶段推进→completed;precheck 拒绝路径 PASS
|
||||
- [ ] 齐套断言:manifest 60 任务 ↔ `templates/docs/rd_*.md` 存在非空;rd_ 键与既有任务键不相交
|
||||
- [ ] web `npm test && npm run build` PASS;Playwright 抽检激活/推进
|
||||
- [ ] 真实 PI + MiniMax 三样本 E2E 报告归档 `.doc/test-reports/`
|
||||
- [ ] P4:旧 live 实例(旧键)+ 新实例(新键)双路径回归 PASS;全工作流任务键集合无交集
|
||||
|
||||
## 相关链接
|
||||
|
||||
- 设计方案: [07-研发工作流rdev接入-设计与实施计划](../../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md)
|
||||
- 参考调研: [2026-09-17-rdev-workflow-reference-analysis](../research/2026-09-17-rdev-workflow-reference-analysis.md)
|
||||
- 二期 backlog: [2026-09-17-rdev-phase2-backlog](../../backlog/2026-09-17-rdev-phase2-backlog.md)
|
||||
- Checklist: -
|
||||
- Handoff: -
|
||||
- Test report: -
|
||||
|
||||
|
||||
## 证据 / 备注
|
||||
|
||||
- 参考项目:`.refs/weichen-agent-system`(configs/workflows/rdev.yaml 60 stage、templates/docs 60 模板、squads/overlays 配套)
|
||||
- 关键约束:WF-DEF-06(无流程级依赖,precheck 先例 test_manager);doc_id `doc_proj{pid}_{taskKey}` 平铺命名 → rd_ 前缀
|
||||
@@ -0,0 +1,30 @@
|
||||
package bizdev
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolveDocStage_PrefersStageKey(t *testing.T) {
|
||||
got := resolveDocStage(TaskRunRequest{TaskKey: "intake", StageKey: "RD01_planning"})
|
||||
if got != "RD01_planning" {
|
||||
t.Fatalf("got %q, want RD01_planning", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDocStage_MainTaskFallback(t *testing.T) {
|
||||
cases := []struct {
|
||||
task string
|
||||
want string
|
||||
}{
|
||||
{"intake", string(StageV01_Triage)},
|
||||
{"research", string(StageV05_Feasibility)},
|
||||
{"feasibility_dev", string(StageV05_Feasibility)},
|
||||
{"cdo_quote", string(StageV10_Quoting)},
|
||||
{"system_reqs", string(StageV05_Feasibility)},
|
||||
{"", string(StageV05_Feasibility)},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := resolveDocStage(TaskRunRequest{TaskKey: tc.task})
|
||||
if got != tc.want {
|
||||
t.Fatalf("task=%q got %q want %q", tc.task, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,6 +287,24 @@ func (s *Service) ListTasks() []TaskMeta {
|
||||
return s.squad.ListTasks()
|
||||
}
|
||||
|
||||
// resolveDocStage 解析写入文档账本的 Stage 字段。
|
||||
// 优先请求透传的冻结实例阶段键;否则按 main 三阶段任务映射;再回落 V0.5_feasibility。
|
||||
func resolveDocStage(req TaskRunRequest) string {
|
||||
if sk := strings.TrimSpace(req.StageKey); sk != "" {
|
||||
return sk
|
||||
}
|
||||
switch strings.TrimSpace(req.TaskKey) {
|
||||
case "intake":
|
||||
return string(StageV01_Triage)
|
||||
case "research", "feasibility_dev", "feasibility_reg", "user_requirements", "prelim_design":
|
||||
return string(StageV05_Feasibility)
|
||||
case "cdo_quote":
|
||||
return string(StageV10_Quoting)
|
||||
default:
|
||||
return string(StageV05_Feasibility)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) ListStages() []StageMeta {
|
||||
return SupportedStages
|
||||
}
|
||||
@@ -476,17 +494,18 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
var appliedProposalID string
|
||||
|
||||
if s.docGov != nil && strings.TrimSpace(docID) != "" {
|
||||
docStage := resolveDocStage(req)
|
||||
var createErr error
|
||||
if s.createDocumentHook != nil {
|
||||
docRec, createErr = s.createDocumentHook(
|
||||
ctx, docID, req.ProjectID, req.TaskKey, artifactTitle,
|
||||
"V0.5_feasibility", req.WorkDir,
|
||||
docStage, req.WorkDir,
|
||||
authorID, authorName, reviewerID, reviewerName, approverID, approverName,
|
||||
)
|
||||
} else {
|
||||
docRec, createErr = s.docGov.CreateOrGetDocument(
|
||||
ctx, docID, req.ProjectID, req.TaskKey, artifactTitle,
|
||||
"V0.5_feasibility", req.WorkDir,
|
||||
docStage, req.WorkDir,
|
||||
authorID, authorName, reviewerID, reviewerName, approverID, approverName,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ type TaskRunRequest struct {
|
||||
UserContext string `json:"user_context,omitempty"`
|
||||
MemoryBankFiles map[string]string `json:"memory_bank_files,omitempty"`
|
||||
DocID string `json:"doc_id,omitempty"`
|
||||
StageKey string `json:"stage_key,omitempty"` // 冻结实例阶段键;空则 resolveDocStage 回落
|
||||
AuthorID int64 `json:"author_id,omitempty"`
|
||||
AuthorName string `json:"author_name,omitempty"`
|
||||
ReviewerID int64 `json:"reviewer_id,omitempty"`
|
||||
|
||||
@@ -68,6 +68,7 @@ type TaskRunRequestDTO struct {
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
ProjectID int64 `json:"project_id,omitempty"`
|
||||
DocID string `json:"doc_id,omitempty"`
|
||||
StageKey string `json:"stage_key,omitempty"` // 冻结实例阶段键(服务端 ResolveDocTask 注入)
|
||||
AuthorID int64 `json:"author_id,omitempty"`
|
||||
AuthorName string `json:"author_name,omitempty"`
|
||||
ReviewerID int64 `json:"reviewer_id,omitempty"`
|
||||
|
||||
@@ -201,6 +201,9 @@ func (s *Server) handleCreateTaskRun(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
taskReq := s.buildBizdevTaskRequest(req.TaskRunRequestDTO, req.SessionID, req.ClientTurnID, claimed.RunID)
|
||||
if taskScope != nil && strings.TrimSpace(taskScope.StageKey) != "" {
|
||||
taskReq.StageKey = taskScope.StageKey
|
||||
}
|
||||
resumeDir := req.WorkDir
|
||||
stageReq := s.buildBizdevStageRequest(req.TaskRunRequestDTO, req.SessionID, req.ClientTurnID, claimed.RunID)
|
||||
createdRun, err := s.taskRuns.StartClaimedRun(r.Context(), claimed, taskReq, stageReq, resumeDir)
|
||||
@@ -592,6 +595,7 @@ func (s *Server) handleResumeTaskRun(w http.ResponseWriter, r *http.Request) {
|
||||
UserID: rec.UserID,
|
||||
ProjectID: rec.ProjectID,
|
||||
TaskKey: rec.TaskKey,
|
||||
StageKey: rec.WorkflowStageKey,
|
||||
}, rec.SessionID, rec.ClientTurnID, rec.RunID)
|
||||
stageReq := s.buildBizdevStageRequest(TaskRunRequestDTO{
|
||||
UserID: rec.UserID,
|
||||
@@ -653,6 +657,7 @@ func (s *Server) buildBizdevTaskRequest(dto TaskRunRequestDTO, sessionID, client
|
||||
ClientTurnID: clientTurnID,
|
||||
RunID: runID,
|
||||
DocID: dto.DocID,
|
||||
StageKey: dto.StageKey,
|
||||
AuthorID: dto.AuthorID,
|
||||
AuthorName: dto.AuthorName,
|
||||
ReviewerID: dto.ReviewerID,
|
||||
|
||||
@@ -45,3 +45,21 @@ func TestBuildBizdevStageRequest_PreservesIdentityAndRoles(t *testing.T) {
|
||||
t.Fatalf("stage=%q", got.Stage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBizdevTaskRequest_PassesStageKey(t *testing.T) {
|
||||
s := &Server{}
|
||||
dto := TaskRunRequestDTO{
|
||||
TaskKey: "rd_proposal",
|
||||
Backend: "stub",
|
||||
UserID: 1,
|
||||
ProjectID: 100,
|
||||
StageKey: "RD01_planning",
|
||||
}
|
||||
got := s.buildBizdevTaskRequest(dto, "sess", "turn", "run")
|
||||
if got.StageKey != "RD01_planning" {
|
||||
t.Fatalf("StageKey=%q, want RD01_planning", got.StageKey)
|
||||
}
|
||||
if got.TaskKey != "rd_proposal" {
|
||||
t.Fatalf("TaskKey=%q", got.TaskKey)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user