package runtime import ( "context" "fmt" ) // TaskResolution 从冻结实例解析的运行时任务绑定。 type TaskResolution struct { ProjectID int64 WorkflowInstID int64 StageKey string TaskKey string DocTaskInstID int64 ExecutorKey string QualityGateKey string TemplateAsset string TemplateVersion int AuthorUserID int64 ReviewerUserID int64 ApproverUserID int64 } // LiveInstancePort is the runtime-owned port for reading a live instance snapshot. // Implementations are wired at the composition root; runtime must not import projectinstance. type LiveInstancePort interface { Available() bool GetLiveInstanceDetail(ctx context.Context, projectID int64) (*LiveInstanceView, *VersionView, error) } // LiveInstanceView is the runtime-facing projection of a live instance. type LiveInstanceView struct { InstanceID int64 ConfigSnapshot WorkflowPlanView Stages []StageInstanceView } // WorkflowPlanView carries frozen plan duties needed for ResolveDocTask. type WorkflowPlanView struct { Stages []StagePlanView } // StagePlanView is one stage in the frozen plan. type StagePlanView struct { StageKey string DocTasks []DocTaskPlanView } // DocTaskPlanView carries duty assignments for one doc task. type DocTaskPlanView struct { TaskKey string Duties DutyView } // DutyView is the three-role assignment on a frozen doc task. type DutyView struct { AuthorUserID int64 ReviewerUserID int64 ApproverUserID int64 } // StageInstanceView is one materialized stage in the live snapshot. type StageInstanceView struct { StageKey string DocTasks []DocTaskInstanceView } // DocTaskInstanceView is one materialized doc task instance. type DocTaskInstanceView struct { ID int64 TaskKey string } // VersionView is the published definition projection needed for executor/template lookup. type VersionView struct { Definition VersionDefinitionView } // VersionDefinitionView holds stages/doc-task defs for lookup. type VersionDefinitionView struct { Stages []StageDefView } // StageDefView is one stage definition. type StageDefView struct { Key string DocTasks []DocTaskDefView } // DocTaskDefView carries executor/template bindings from the published definition. type DocTaskDefView struct { Key string ExecutorKey string QualityGateKey string TemplateAssetKey string TemplateVersion int Template *TemplateRefView } // TemplateRefView is an optional template version ref. type TemplateRefView struct { AssetKey string Version int } // Orchestrator 只读冻结快照编排(禁止 flatten 旁路)。 type Orchestrator struct { live LiveInstancePort } func NewOrchestrator(live LiveInstancePort) *Orchestrator { return &Orchestrator{live: live} } // ResolveDocTask 由 doc_task_instance_id 解析 executor/模板/三权。 func (o *Orchestrator) ResolveDocTask(ctx context.Context, projectID, docTaskInstID int64) (*TaskResolution, error) { if o == nil || o.live == nil || !o.live.Available() { return nil, fmt.Errorf("orchestrator unavailable") } detail, version, err := o.live.GetLiveInstanceDetail(ctx, projectID) if err != nil { return nil, err } if detail == nil || version == nil { return nil, fmt.Errorf("orchestrator: empty live snapshot") } plan := detail.ConfigSnapshot for _, st := range detail.Stages { for _, dt := range st.DocTasks { if dt.ID != docTaskInstID { continue } res := &TaskResolution{ ProjectID: projectID, WorkflowInstID: detail.InstanceID, StageKey: st.StageKey, TaskKey: dt.TaskKey, DocTaskInstID: dt.ID, } if def := lookupTaskDef(&version.Definition, st.StageKey, dt.TaskKey); def != nil { res.ExecutorKey = def.ExecutorKey res.QualityGateKey = def.QualityGateKey if def.Template != nil { res.TemplateAsset = def.Template.AssetKey res.TemplateVersion = def.Template.Version } else { res.TemplateAsset = def.TemplateAssetKey res.TemplateVersion = def.TemplateVersion } } matchedDuty := false for _, stPlan := range plan.Stages { if stPlan.StageKey != st.StageKey { continue } for _, dtPlan := range stPlan.DocTasks { if dtPlan.TaskKey != dt.TaskKey { continue } res.AuthorUserID = dtPlan.Duties.AuthorUserID res.ReviewerUserID = dtPlan.Duties.ReviewerUserID res.ApproverUserID = dtPlan.Duties.ApproverUserID matchedDuty = true } } if !matchedDuty { return nil, fmt.Errorf("orchestrator: frozen plan has no duties for stage %s task %s", st.StageKey, dt.TaskKey) } return res, nil } } return nil, fmt.Errorf("doc task instance not in live snapshot") } func lookupTaskDef(def *VersionDefinitionView, stageKey, taskKey string) *DocTaskDefView { if def == nil { return nil } for _, st := range def.Stages { if st.Key != stageKey { continue } for i := range st.DocTasks { if st.DocTasks[i].Key == taskKey { return &st.DocTasks[i] } } } return nil }