package storage import ( "encoding/json" "strings" "testing" "github.com/thepeterstone/claudomator/internal/role" ) func TestSeedRoleConfigs_CreatesAndActivatesBuilder(t *testing.T) { db := testDB(t) if err := db.SeedRoleConfigs(); err != nil { t.Fatalf("SeedRoleConfigs: %v", err) } row, err := db.GetActiveRoleConfig("builder") if err != nil { t.Fatalf("GetActiveRoleConfig(builder): %v", err) } if row.Version != 1 { t.Errorf("expected version 1, got %d", row.Version) } if row.ProposedBy != "seed" { t.Errorf("expected proposed_by 'seed', got %q", row.ProposedBy) } var cfg role.RoleConfig if err := json.Unmarshal([]byte(row.ConfigJSON), &cfg); err != nil { t.Fatalf("unmarshal seeded config: %v", err) } if cfg.Role != "builder" { t.Errorf("cfg.Role = %q, want builder", cfg.Role) } if cfg.SystemPrompt == "" { t.Error("cfg.SystemPrompt is empty, want the decompose-or-implement guidance") } if len(cfg.EscalationLadder) != 2 { t.Fatalf("expected 2 escalation tiers, got %d", len(cfg.EscalationLadder)) } if cfg.EscalationLadder[0].Candidates[0].Provider != "claude" { t.Errorf("tier 0 provider = %q, want claude", cfg.EscalationLadder[0].Candidates[0].Provider) } } // TestSeedRoleConfigs_CreatesAndActivatesPlanner proves the arbitration // role's system prompt is seeded too (added 2026-07-11 alongside // finalizeArbitration's fail-closed default -- see that const's doc // comment for why: a live run showed an arbitration agent with no system // prompt at all never called report_verdict). Specifically checks the // prompt actually names the report_verdict tool, since that's the whole // point of this seed. func TestSeedRoleConfigs_CreatesAndActivatesPlanner(t *testing.T) { db := testDB(t) if err := db.SeedRoleConfigs(); err != nil { t.Fatalf("SeedRoleConfigs: %v", err) } row, err := db.GetActiveRoleConfig("planner") if err != nil { t.Fatalf("GetActiveRoleConfig(planner): %v", err) } if row.Version != 1 { t.Errorf("expected version 1, got %d", row.Version) } var cfg role.RoleConfig if err := json.Unmarshal([]byte(row.ConfigJSON), &cfg); err != nil { t.Fatalf("unmarshal seeded config: %v", err) } if cfg.Role != "planner" { t.Errorf("cfg.Role = %q, want planner", cfg.Role) } if !strings.Contains(cfg.SystemPrompt, "report_verdict") { t.Error("cfg.SystemPrompt does not mention report_verdict -- the whole point of this seed is mandating that call") } if len(cfg.EscalationLadder) != 2 { t.Fatalf("expected 2 escalation tiers, got %d", len(cfg.EscalationLadder)) } } func TestSeedRoleConfigs_Idempotent_DoesNotDuplicateOrOverwrite(t *testing.T) { db := testDB(t) if err := db.SeedRoleConfigs(); err != nil { t.Fatalf("first SeedRoleConfigs: %v", err) } if err := db.SeedRoleConfigs(); err != nil { t.Fatalf("second SeedRoleConfigs: %v", err) } for _, r := range []string{"builder", "planner"} { versions, err := db.ListRoleConfigVersions(r) if err != nil { t.Fatalf("ListRoleConfigVersions(%s): %v", r, err) } if len(versions) != 1 { t.Fatalf("%s: expected exactly 1 version after calling SeedRoleConfigs twice, got %d", r, len(versions)) } } } func TestSeedRoleConfigs_DoesNotOverwriteHumanCustomizedVersion(t *testing.T) { db := testDB(t) // A human (or the retro role's propose_role_config tool) already // created and activated their own version before the seed ever ran. custom, err := db.CreateRoleConfig("builder", `{"role":"builder","system_prompt":"custom prompt"}`, "human") if err != nil { t.Fatalf("CreateRoleConfig: %v", err) } if err := db.ActivateRoleConfigVersion("builder", custom.Version); err != nil { t.Fatalf("ActivateRoleConfigVersion: %v", err) } if err := db.SeedRoleConfigs(); err != nil { t.Fatalf("SeedRoleConfigs: %v", err) } row, err := db.GetActiveRoleConfig("builder") if err != nil { t.Fatalf("GetActiveRoleConfig: %v", err) } if row.ProposedBy != "human" { t.Errorf("active role config proposed_by = %q, want unchanged 'human' -- seed must not overwrite a human-authored active version", row.ProposedBy) } versions, err := db.ListRoleConfigVersions("builder") if err != nil { t.Fatalf("ListRoleConfigVersions: %v", err) } if len(versions) != 1 { t.Fatalf("expected exactly 1 version (seed must not create a competing draft), got %d", len(versions)) } }