Close P0 auth gaps for lineage/runs, stage ABAC fail-closed, and project:create; add IAM migrations 00021-23, me settings APIs, schema v2 manifests, orchestrator skeleton, and gate A regression coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"chat-agent-system/internal/projectinstance"
|
|
"chat-agent-system/internal/workflowdef"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// Orchestrator 只读冻结快照编排(禁止 flatten 旁路)。
|
|
type Orchestrator struct {
|
|
instSvc *projectinstance.Service
|
|
}
|
|
|
|
func NewOrchestrator(instSvc *projectinstance.Service) *Orchestrator {
|
|
return &Orchestrator{instSvc: instSvc}
|
|
}
|
|
|
|
// ResolveDocTask 由 doc_task_instance_id 解析 executor/模板/三权。
|
|
func (o *Orchestrator) ResolveDocTask(ctx context.Context, projectID, docTaskInstID int64) (*TaskResolution, error) {
|
|
if o == nil || o.instSvc == nil || !o.instSvc.Available() {
|
|
return nil, fmt.Errorf("orchestrator unavailable")
|
|
}
|
|
detail, version, err := o.instSvc.GetLiveInstanceDetail(ctx, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plan := detail.Instance.ConfigSnapshot
|
|
for _, st := range detail.Stages {
|
|
for _, dt := range st.DocTasks {
|
|
if dt.ID != docTaskInstID {
|
|
continue
|
|
}
|
|
res := &TaskResolution{
|
|
ProjectID: projectID,
|
|
WorkflowInstID: detail.Instance.ID,
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("doc task instance not in live snapshot")
|
|
}
|
|
|
|
func lookupTaskDef(def *workflowdef.VersionDefinition, stageKey, taskKey string) *workflowdef.DocTaskDef {
|
|
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
|
|
}
|