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 } } } }