Introduce Phase 2 IAM-adjacent schema for versioned workflow definitions, template assets/versions, and workflow_defs permissions seed. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
3.2 KiB
Go
143 lines
3.2 KiB
Go
package workflowdef
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
|
|
"chat-agent-system/internal/projectworkflow"
|
|
)
|
|
|
|
// DBCatalog 从已发布工作流版本构建 projectworkflow.Catalog(向后兼容扁平 Definition)。
|
|
type DBCatalog struct {
|
|
mu sync.RWMutex
|
|
store *Store
|
|
items map[string]*projectworkflow.Definition
|
|
order []string
|
|
}
|
|
|
|
func NewDBCatalog(ctx context.Context, store *Store) (*DBCatalog, error) {
|
|
c := &DBCatalog{store: store, items: make(map[string]*projectworkflow.Definition)}
|
|
if err := c.reload(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *DBCatalog) reload(ctx context.Context) error {
|
|
versions, err := c.store.ListPublishedCatalogEntries(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
items := make(map[string]*projectworkflow.Definition)
|
|
order := make([]string, 0, len(versions))
|
|
for _, v := range versions {
|
|
def := flattenVersion(v)
|
|
if def == nil {
|
|
continue
|
|
}
|
|
items[v.WorkflowKey] = def
|
|
order = append(order, v.WorkflowKey)
|
|
}
|
|
sort.Strings(order)
|
|
c.mu.Lock()
|
|
c.items = items
|
|
c.order = order
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func flattenVersion(v Version) *projectworkflow.Definition {
|
|
if len(v.Definition.Stages) == 0 {
|
|
return nil
|
|
}
|
|
st := v.Definition.Stages[0]
|
|
taskKey, taskTitle, docType := "", "", ""
|
|
if len(st.DocTasks) > 0 {
|
|
dt := st.DocTasks[0]
|
|
taskKey = dt.Key
|
|
taskTitle = dt.OutputDocTitle
|
|
docType = dt.OutputDocType
|
|
if taskTitle == "" {
|
|
taskTitle = dt.Title
|
|
}
|
|
}
|
|
if taskKey == "" {
|
|
taskKey = st.Key
|
|
}
|
|
name := v.WorkflowName
|
|
if name == "" {
|
|
name = v.WorkflowKey
|
|
}
|
|
return &projectworkflow.Definition{
|
|
Key: v.WorkflowKey,
|
|
Name: name,
|
|
Description: v.Description,
|
|
StageKey: st.Key,
|
|
TaskKey: taskKey,
|
|
RequiredUpstreamWorkflow: v.Definition.RequiredUpstreamWorkflow,
|
|
OutputDocType: docType,
|
|
OutputDocTitle: taskTitle,
|
|
Enabled: true,
|
|
}
|
|
}
|
|
|
|
func (c *DBCatalog) HasDefinitions() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return len(c.items) > 0
|
|
}
|
|
|
|
func (c *DBCatalog) Refresh(ctx context.Context) error {
|
|
return c.reload(ctx)
|
|
}
|
|
|
|
func (c *DBCatalog) Get(workflowKey string) (*projectworkflow.Definition, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
d, ok := c.items[workflowKey]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
cpy := *d
|
|
return &cpy, true
|
|
}
|
|
|
|
func (c *DBCatalog) List() []*projectworkflow.Definition {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
out := make([]*projectworkflow.Definition, 0, len(c.order))
|
|
for _, key := range c.order {
|
|
if d, ok := c.items[key]; ok {
|
|
cpy := *d
|
|
out = append(out, &cpy)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *DBCatalog) Validate() error {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
for _, def := range c.items {
|
|
if def.RequiredUpstreamWorkflow != "" {
|
|
if _, ok := c.items[def.RequiredUpstreamWorkflow]; !ok {
|
|
return projectworkflow.ErrWorkflowNotFound
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewCatalog 优先 DB 已发布 catalog,否则回退静态 catalog。
|
|
func NewCatalog(ctx context.Context, store *Store, fallback projectworkflow.Catalog) projectworkflow.Catalog {
|
|
if store == nil || !store.enabled() {
|
|
return fallback
|
|
}
|
|
dbCat, err := NewDBCatalog(ctx, store)
|
|
if err != nil || !dbCat.HasDefinitions() {
|
|
return fallback
|
|
}
|
|
return dbCat
|
|
}
|