Files
Agent-System/internal/workflowdef/bootstrap.go
T
weichenandCursor 079225495e fix(workflowdef): heal retired seeds and fall back published_by
启动种子不再依赖固定用户 id=1,并把白名单工作流的 retired 状态恢复为 active。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 14:03:30 +00:00

160 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}
}
}