Keep registry key validation local to workflowdef with composition-root injection, adapt FlatCatalog for projectworkflow, and drive the orchestrator through LiveInstancePort views instead of projectinstance.
191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
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
|
|
}
|