启动种子不再依赖固定用户 id=1,并把白名单工作流的 retired 状态恢复为 active。 Co-authored-by: Cursor <cursoragent@cursor.com>
160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
package workflowdef
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"log/slog"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/jackc/pgx/v5"
|
||
"github.com/jackc/pgx/v5/pgxpool"
|
||
|
||
"chat-agent-system/internal/storage"
|
||
)
|
||
|
||
var officialSeedMeta = map[string]struct {
|
||
Name string
|
||
Description string
|
||
Domain string
|
||
}{
|
||
OfficialKeyMain: {
|
||
Name: "商务对接流程",
|
||
Description: "V0.1 需求摸底 → V0.5 方案与可行性 → V1.0 商务报价(7 个受控文档)",
|
||
Domain: "medical_device",
|
||
},
|
||
OfficialKeyProductManager: {
|
||
Name: "智能产品经理流程",
|
||
Description: "基于项目上下文独立生成系统需求规格书",
|
||
Domain: "medical_device",
|
||
},
|
||
OfficialKeyTestManager: {
|
||
Name: "智能测试工程师流程",
|
||
Description: "基于项目上下文独立生成系统验证方案及计划",
|
||
Domain: "medical_device",
|
||
},
|
||
OfficialKeyRDev: {
|
||
Name: "研发设计控制流程",
|
||
Description: "ISO 13485 设计控制:13 阶段 / 60 受控文档(DHF/DMR)",
|
||
Domain: "medical_device",
|
||
},
|
||
OfficialKeySoftwareBootstrap: {
|
||
Name: "软件项目自举流程",
|
||
Description: "S0–S4:章程、现状、需求、架构、风险、路线与就绪评审(无医疗语义)",
|
||
Domain: "non_medical_software",
|
||
},
|
||
OfficialKeySoftwareDelivery: {
|
||
Name: "软件交付迭代流程",
|
||
Description: "D0–D4:变更定义、实施证据、验证、发布与复盘(可重复实例)",
|
||
Domain: "non_medical_software",
|
||
},
|
||
}
|
||
|
||
// resolveSeedActor 返回可作为 workflow_versions.created_by 外键的用户 id。
|
||
// 请求的 publishedBy 不存在时回落到库内最小 id;没有任何用户时返回 0。
|
||
func resolveSeedActor(ctx context.Context, pool *pgxpool.Pool, publishedBy int64) (int64, error) {
|
||
if pool == nil {
|
||
return 0, nil
|
||
}
|
||
if publishedBy > 0 {
|
||
var exists bool
|
||
if err := pool.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM users WHERE id = $1)`, publishedBy).Scan(&exists); err != nil {
|
||
return 0, err
|
||
}
|
||
if exists {
|
||
return publishedBy, nil
|
||
}
|
||
}
|
||
var id int64
|
||
err := pool.QueryRow(ctx, `SELECT id FROM users ORDER BY id ASC LIMIT 1`).Scan(&id)
|
||
if errors.Is(err, pgx.ErrNoRows) {
|
||
return 0, nil
|
||
}
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return id, nil
|
||
}
|
||
|
||
// BootstrapSeeds 幂等导入模板与正式 v2 工作流;非正式定义标记为不可选。
|
||
func BootstrapSeeds(ctx context.Context, pool *pgxpool.Pool, cas *storage.CAS, templatesDir string, publishedBy int64) error {
|
||
if pool == nil || !TablesReady(ctx, pool) {
|
||
return nil
|
||
}
|
||
actor, err := resolveSeedActor(ctx, pool, publishedBy)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if actor == 0 {
|
||
slog.Warn("workflow seed skipped: no users available for published_by")
|
||
return nil
|
||
}
|
||
if actor != publishedBy {
|
||
slog.Warn("workflow seed published_by fallback", "requested", publishedBy, "actor", actor)
|
||
}
|
||
publishedBy = actor
|
||
store := NewStore(pool)
|
||
svc := NewService(pool, cas)
|
||
|
||
if templatesDir == "" {
|
||
templatesDir = "templates/docs"
|
||
}
|
||
entries, err := os.ReadDir(templatesDir)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
for _, ent := range entries {
|
||
if ent.IsDir() || !strings.HasSuffix(strings.ToLower(ent.Name()), ".md") {
|
||
continue
|
||
}
|
||
key := strings.TrimSuffix(ent.Name(), filepath.Ext(ent.Name()))
|
||
data, readErr := os.ReadFile(filepath.Join(templatesDir, ent.Name()))
|
||
if readErr != nil || len(data) == 0 {
|
||
continue
|
||
}
|
||
_, _ = svc.UploadTemplate(ctx, key, ent.Name(), data, ent.Name(), publishedBy)
|
||
}
|
||
|
||
for _, key := range OfficialWorkflowKeys() {
|
||
meta := officialSeedMeta[key]
|
||
filename := V2ManifestFiles[key]
|
||
def, loadErr := LoadManifestDefinition(filename)
|
||
if loadErr != nil {
|
||
return loadErr
|
||
}
|
||
normalizeManifestDefinition(&def)
|
||
if err := store.UpsertPublishedSeed(ctx, CreateDefinitionInput{
|
||
Key: key,
|
||
Name: meta.Name,
|
||
Description: meta.Description,
|
||
ProductDomain: meta.Domain,
|
||
Definition: def,
|
||
}, publishedBy); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return store.RetireNonOfficialDefinitions(ctx)
|
||
}
|
||
|
||
func normalizeManifestDefinition(def *VersionDefinition) {
|
||
if def == nil {
|
||
return
|
||
}
|
||
def.RequiredUpstreamWorkflow = ""
|
||
for i := range def.Stages {
|
||
for j := range def.Stages[i].DocTasks {
|
||
dt := &def.Stages[i].DocTasks[j]
|
||
if dt.CreatorConfigurable || dt.BlockingWhenEnabled || dt.DefaultEnabled {
|
||
continue
|
||
}
|
||
if dt.Required || dt.Blocking {
|
||
dt.CreatorConfigurable = true
|
||
dt.BlockingWhenEnabled = true
|
||
dt.DefaultEnabled = true
|
||
}
|
||
}
|
||
}
|
||
}
|