diff --git a/internal/app/runtime.go b/internal/app/runtime.go index a2ce7aa..12cec95 100644 --- a/internal/app/runtime.go +++ b/internal/app/runtime.go @@ -280,7 +280,6 @@ func gitConfigForReviewerAuth(profile config.Profile) config.GitConfig { AuthMode: profile.ReviewerCredentials.AuthMode, Credential: profile.ReviewerCredentials.Credential, GitHubApp: githubApp, - CredentialRef: profile.ReviewerCredentials.CredentialRef, IdentityCache: profile.ReviewerCredentials.IdentityCache, } } diff --git a/internal/app/runtime_test.go b/internal/app/runtime_test.go index 90c6dab..bfc6436 100644 --- a/internal/app/runtime_test.go +++ b/internal/app/runtime_test.go @@ -65,9 +65,8 @@ func TestOpenUsesReviewerCredentialsAsRuntimeProvider(t *testing.T) { cfg := testConfig() profile := cfg.Profiles["home"] profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home-reviewer"}, - CredentialRef: "codereview/home-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home-reviewer"}, } cfg.Profiles["home"] = profile @@ -85,7 +84,7 @@ func TestOpenUsesReviewerCredentialsAsRuntimeProvider(t *testing.T) { Dependencies: testDependencies(t, func(git config.GitConfig, _ credentials.Reader, _ githubprovider.Options) (gitprovider.GitProvider, gitprovider.Credential, error) { providerCalls = append(providerCalls, git) - if git.CredentialRef == "codereview/home-reviewer" { + if git.Credential.Name == "codereview/home-reviewer" { return reviewerProvider, gitprovider.Credential{Type: "pat", Token: "reviewer-token"}, nil } return repoProvider, gitprovider.Credential{Type: "pat", Token: "repo-token"}, nil @@ -108,8 +107,8 @@ func TestOpenUsesReviewerCredentialsAsRuntimeProvider(t *testing.T) { defer runtime.Cleanup() } if len(providerCalls) != 2 || - providerCalls[0].CredentialRef != "codereview/home" || - providerCalls[1].CredentialRef != "codereview/home-reviewer" { + providerCalls[0].Credential.Name != "codereview/home" || + providerCalls[1].Credential.Name != "codereview/home-reviewer" { t.Fatalf("provider calls = %#v, want repository read then reviewer posting providers", providerCalls) } runner, ok := runtime.Runner.(reviewRunner) @@ -414,11 +413,9 @@ func TestOpenSelectionPassesGitHubAppInstallationLookupAndPinnedID(t *testing.T) profile := cfg.Profiles["home"] profile.Git.AuthMode = config.GitAuthModeGitHubApp profile.Git.Credential = credential - profile.Git.CredentialRef = "codereview/cr-reviewer" profile.ReviewerCredentials = &config.ReviewerCredentials{ // #nosec G101 -- test credential reference, not secret material. - AuthMode: config.GitAuthModeGitHubApp, - Credential: credential, - CredentialRef: "codereview/cr-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + Credential: credential, } profile.Reviewer = config.ProfileReviewer{ Kind: config.ProfileReviewerKindEntity, @@ -611,7 +608,6 @@ func TestOpenInjectsCachedReaderIntoProviderAndAdapter(t *testing.T) { profile.LLM.Auth = config.LLMAuthAPIKey profile.LLM.Adapter = config.LLMAdapterOpenAIAPI profile.LLM.Credential = config.CredentialLocation{Store: "test-memory", Name: "codereview/home"} - profile.LLM.CredentialRef = "codereview/home" cfg.Profiles["home"] = profile var providerReader credentials.Reader @@ -624,7 +620,7 @@ func TestOpenInjectsCachedReaderIntoProviderAndAdapter(t *testing.T) { PRRef: testPRRef(), Dependencies: testDependencies(t, func(git config.GitConfig, reader credentials.Reader, _ githubprovider.Options) (gitprovider.GitProvider, gitprovider.Credential, error) { - if git.CredentialRef == "codereview/home" && providerReader == nil { + if git.Credential.Name == "codereview/home" && providerReader == nil { providerReader = reader } else { postingReader = reader @@ -670,7 +666,7 @@ func TestOpenSelectionInjectsCachedReaderIntoProviderAndAdapter(t *testing.T) { profile.LLM.Auth = config.LLMAuthAPIKey profile.LLM.Adapter = config.LLMAdapterOpenAIAPI profile.LLM.Credential = config.CredentialLocation{Store: "test-memory", Name: "codereview/home"} - profile.LLM.CredentialRef = "codereview/home" + profile.LLM.Credential.Name = "codereview/home" cfg.Profiles["home"] = profile var providerReader credentials.Reader @@ -937,10 +933,9 @@ func testConfig() config.File { configtest.WithoutRepositoryProfiles(), configtest.HomeProfile(config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory"}, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, diff --git a/internal/cmd/agentscmd/agentscmd_test.go b/internal/cmd/agentscmd/agentscmd_test.go index fb8dc15..99ba619 100644 --- a/internal/cmd/agentscmd/agentscmd_test.go +++ b/internal/cmd/agentscmd/agentscmd_test.go @@ -104,7 +104,7 @@ func TestAgentsListWithPRUsesRepositoryProfileRoute(t *testing.T) { fake, ref := fakeProviderWithRepoAgent(t, "repo", "reviewer", "repo desc") cfg := testConfig("") work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -115,8 +115,8 @@ func TestAgentsListWithPRUsesRepositoryProfileRoute(t *testing.T) { }, }} cmd, out := newTestCommand(t, cfg, func(_ *cobra.Command, _ *root.Options, _ config.File, profile config.Profile) (gitprovider.GitProvider, func(), error) { - if profile.Git.CredentialRef != "codereview/work" { - t.Fatalf("provider profile credential ref = %q, want work route", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/work" { + t.Fatalf("provider profile credential ref = %q, want work route", profile.Git.Credential.Name) } return fake, nil, nil }) @@ -137,7 +137,7 @@ func TestAgentsListWithPRRejectsAmbiguousRepositoryProfileRoute(t *testing.T) { fake, ref := fakeProviderWithRepoAgent(t, "repo", "reviewer", "repo desc") cfg := testConfig("") work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{ { @@ -175,7 +175,7 @@ func TestAgentsListExplicitProfileBypassesRepositoryRoute(t *testing.T) { fake, ref := fakeProviderWithRepoAgent(t, "repo", "reviewer", "repo desc") cfg := testConfig("") work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -186,8 +186,8 @@ func TestAgentsListExplicitProfileBypassesRepositoryRoute(t *testing.T) { }, }} cmd, _ := newTestCommand(t, cfg, func(_ *cobra.Command, _ *root.Options, _ config.File, profile config.Profile) (gitprovider.GitProvider, func(), error) { - if profile.Git.CredentialRef != "codereview/home" { - t.Fatalf("provider profile credential ref = %q, want explicit home", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/home" { + t.Fatalf("provider profile credential ref = %q, want explicit home", profile.Git.Credential.Name) } return fake, nil, nil }) @@ -201,7 +201,7 @@ func TestAgentsShowWithPRUsesRepositoryProfileRoute(t *testing.T) { fake, ref := fakeProviderWithRepoAgent(t, "repo", "reviewer", "repo desc") cfg := testConfig("") work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -212,8 +212,8 @@ func TestAgentsShowWithPRUsesRepositoryProfileRoute(t *testing.T) { }, }} cmd, out := newTestCommand(t, cfg, func(_ *cobra.Command, _ *root.Options, _ config.File, profile config.Profile) (gitprovider.GitProvider, func(), error) { - if profile.Git.CredentialRef != "codereview/work" { - t.Fatalf("provider profile credential ref = %q, want work route", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/work" { + t.Fatalf("provider profile credential ref = %q, want work route", profile.Git.Credential.Name) } return fake, nil, nil }) @@ -234,7 +234,7 @@ func TestAgentsListExplicitEmptyProfileFailsBeforeRepositoryRoute(t *testing.T) fake, ref := fakeProviderWithRepoAgent(t, "repo", "reviewer", "repo desc") cfg := testConfig("") work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -264,7 +264,7 @@ func TestAgentsListExplicitProfileHostMismatch(t *testing.T) { cfg.RepositoryProfiles = nil work := home work.Git.Host = "github.com" - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -452,9 +452,9 @@ func providerFactory(provider gitprovider.GitProvider) ProviderFactory { func testConfig(agentSource string) config.File { profile := config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, diff --git a/internal/cmd/benchmarkcmd/benchmarkcmd_test.go b/internal/cmd/benchmarkcmd/benchmarkcmd_test.go index 77906e0..2245523 100644 --- a/internal/cmd/benchmarkcmd/benchmarkcmd_test.go +++ b/internal/cmd/benchmarkcmd/benchmarkcmd_test.go @@ -1125,7 +1125,7 @@ func testConfig() config.File { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, IdentityCache: "review-bot", }, LLM: config.LLMConfig{ diff --git a/internal/cmd/configcmd/configcmd_test.go b/internal/cmd/configcmd/configcmd_test.go index 5c1f9ba..e534135 100644 --- a/internal/cmd/configcmd/configcmd_test.go +++ b/internal/cmd/configcmd/configcmd_test.go @@ -1400,7 +1400,7 @@ func TestConfigShowGitHubAppGitCredentialStatus(t *testing.T) { work := cfg.Profiles["work"] work.Git.AuthMode = config.GitAuthModeGitHubApp work.Git.GitHubApp = &config.GitHubAppConfig{AppID: "12345"} - work.Git.CredentialRef = "codereview/work-app" + work.Git.Credential.Name = "codereview/work-app" work.Git.Credential.Name = "codereview/work-app" cfg.Profiles["work"] = work path := saveTestConfig(t, cfg) @@ -2492,7 +2492,7 @@ func TestConfigClearGitHubAppCredentialMatrix(t *testing.T) { work := cfg.Profiles["work"] work.Git.AuthMode = config.GitAuthModeGitHubApp work.Git.GitHubApp = &config.GitHubAppConfig{AppID: "12345"} - work.Git.CredentialRef = "codereview/work-app" + work.Git.Credential.Name = "codereview/work-app" work.Git.Credential.Name = "codereview/work-app" cfg.Profiles["work"] = work path := saveTestConfig(t, cfg) @@ -2601,11 +2601,11 @@ func TestConfigClearAllDryRunTextReportsPredictedReset(t *testing.T) { func TestConfigClearAllClearsOnlySelectedProfileAndRemovesCache(t *testing.T) { cfg := fileBackendConfig(t) alpha := cfg.Profiles["home"] - alpha.Git.CredentialRef = "codereview/alpha" + alpha.Git.Credential.Name = "codereview/alpha" alpha.Git.Credential.Name = "codereview/alpha" cfg.Profiles["alpha"] = alpha beta := cfg.Profiles["home"] - beta.Git.CredentialRef = "codereview/beta" + beta.Git.Credential.Name = "codereview/beta" beta.Git.Credential.Name = "codereview/beta" cfg.Profiles["beta"] = beta path := saveTestConfig(t, cfg) @@ -2901,7 +2901,7 @@ func TestConfigClearProfileAllClearsOnlySelectedProfile(t *testing.T) { func TestConfigClearProfileAllPrunesSelectedProfileRoutes(t *testing.T) { cfg := fileBackendConfig(t) alpha := cfg.Profiles["home"] - alpha.Git.CredentialRef = "codereview/alpha" + alpha.Git.Credential.Name = "codereview/alpha" alpha.Git.Credential.Name = "codereview/alpha" cfg.Profiles["alpha"] = alpha cfg.RepositoryProfiles = []config.RepositoryProfile{ @@ -3411,32 +3411,28 @@ func testConfig() config.File { configtest.WithoutRepositoryProfiles(), configtest.HomeProfile(config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, }, LLM: config.LLMConfig{Provider: config.LLMProviderAnthropic, Auth: config.LLMAuthSubscription, Adapter: config.LLMAdapterClaudeCLI}, ReviewPolicy: config.ReviewPolicy{MajorEvent: config.ReviewMajorEventComment}, }), configtest.Profile("work", config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work"}, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-reviewer"}, }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-llm"}, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-llm"}, }, ReviewPolicy: config.ReviewPolicy{MajorEvent: config.ReviewMajorEventRequestChanges}, }), diff --git a/internal/cmd/credentialcmd/credentialcmd_test.go b/internal/cmd/credentialcmd/credentialcmd_test.go index 1faec10..9f6e5ed 100644 --- a/internal/cmd/credentialcmd/credentialcmd_test.go +++ b/internal/cmd/credentialcmd/credentialcmd_test.go @@ -205,8 +205,7 @@ func TestSetCredentialUsesGitHubAppCredentialMatrix(t *testing.T) { Store: testFileCredentialStoreID, Name: "codereview/work-reviewer", }, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, } cfg.Profiles["work"] = work saveCredentialTestConfig(t, path, cfg) @@ -597,10 +596,9 @@ func basicProfile(profile string) config.Profile { } return config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, - CredentialRef: ref, + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -614,11 +612,10 @@ func apiKeyProfile(profile string, provider config.LLMProvider) config.Profile { p := basicProfile(profile) ref := "codereview/" + profile + "-llm" p.LLM = config.LLMConfig{ - Provider: provider, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, - CredentialRef: ref, + Provider: provider, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, } if provider == config.LLMProviderOpenAI { p.LLM.Adapter = config.LLMAdapterOpenAIAPI diff --git a/internal/cmd/datacmd/datacmd_test.go b/internal/cmd/datacmd/datacmd_test.go index d367c8a..e85676b 100644 --- a/internal/cmd/datacmd/datacmd_test.go +++ b/internal/cmd/datacmd/datacmd_test.go @@ -221,9 +221,9 @@ func TestDataPruneDefaultIgnoresConfiguredRetention(t *testing.T) { Profiles: map[string]config.Profile{ "home": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, diff --git a/internal/cmd/initcmd/initcmd.go b/internal/cmd/initcmd/initcmd.go index 4b396f1..0f0ea7b 100644 --- a/internal/cmd/initcmd/initcmd.go +++ b/internal/cmd/initcmd/initcmd.go @@ -153,7 +153,6 @@ type initDraft struct { ReviewerCredentialWrites map[string]string ReviewerCredentialOverwrite bool ReviewerCredentialSatisfied bool - SecretsStore string LLMProvider string LLMAuth string LLMAdapter string @@ -1359,8 +1358,8 @@ func stageStandaloneInteractiveInitReviewerEntity(session initSessionDraft, flag previous = &existingCopy } if strings.TrimSpace(entity.CredentialRef) == "" { - if previous != nil && strings.TrimSpace(previous.CredentialRef) != "" { - entity.CredentialRef = strings.TrimSpace(previous.CredentialRef) + if previous != nil && strings.TrimSpace(previous.Credential.Name) != "" { + entity.CredentialRef = strings.TrimSpace(previous.Credential.Name) } else { ref, err := initCredentialRefFromSeed(name) if err != nil { @@ -2319,7 +2318,7 @@ func buildInitSecretsStoreInventory(cfg config.File) ([]config.EffectiveSecretsS selectedByProfile := make(map[string]string, len(cfg.Profiles)) brokenByProfile := map[string]string{} for name, profile := range cfg.Profiles { - selection := strings.TrimSpace(profile.SecretsStore) + selection := strings.TrimSpace(profile.Git.Credential.Store) selectedByProfile[name] = selection if selection == "" { continue @@ -2549,7 +2548,6 @@ func initLLMRuntimeDraftFromSeedDraft(draft initDraft) initLLMRuntimeDraft { Auth: config.LLMAuth(draft.LLMAuth), Adapter: config.LLMAdapter(draft.LLMAdapter), Credential: initCredentialLocationIfName(draft.LLMCredentialStore, draft.LLMCredentialRef), - CredentialRef: strings.TrimSpace(draft.LLMCredentialRef), ModelMap: copyModelMap(draft.ModelMap), ReviewerModelTier: config.ModelTier(strings.TrimSpace(draft.LLMReviewerModelTier)), }) @@ -3108,17 +3106,16 @@ func seedInteractiveInitDraft(requestedProfileName string, existingProfileName s draft.GitHubAppID = strings.TrimSpace(existingProfile.Git.GitHubApp.AppID) } draft.GitCredentialStore = initCredentialStoreDraftValue(existingProfile.Git.Credential.Store) - draft.GitCredentialRef = firstNonEmpty(existingProfile.Git.Credential.Name, existingProfile.Git.CredentialRef) + draft.GitCredentialRef = existingProfile.Git.Credential.Name draft.LLMProvider = string(existingProfile.LLM.Provider) draft.LLMAuth = string(existingProfile.LLM.Auth) draft.LLMAdapter = string(existingProfile.LLM.Adapter) draft.LLMReviewerModelTier = string(existingProfile.LLM.ReviewerModelTier) draft.LLMCredentialStore = initCredentialStoreDraftValue(existingProfile.LLM.Credential.Store) - draft.LLMCredentialRef = firstNonEmpty(existingProfile.LLM.Credential.Name, existingProfile.LLM.CredentialRef) + draft.LLMCredentialRef = existingProfile.LLM.Credential.Name draft.ModelMap = copyModelMap(existingProfile.LLM.ModelMap) draft.AgentSources = append([]string(nil), existingProfile.AgentSources...) draft.ReviewPolicy = existingProfile.ReviewPolicy - draft.SecretsStore = strings.TrimSpace(existingProfile.SecretsStore) if existingProfile.Reviewer.GitHubAppInstallation != nil { installation := existingProfile.Reviewer.GitHubAppInstallation draft.ReviewerGitHubAppInstallationMode = strings.TrimSpace(string(installation.Mode)) @@ -3131,7 +3128,7 @@ func seedInteractiveInitDraft(requestedProfileName string, existingProfileName s draft.ReviewerGitHubAppID = strings.TrimSpace(existingProfile.ReviewerCredentials.GitHubApp.AppID) } draft.ReviewerCredentialStore = initCredentialStoreDraftValue(existingProfile.ReviewerCredentials.Credential.Store) - draft.ReviewerCredentialRef = firstNonEmpty(existingProfile.ReviewerCredentials.Credential.Name, existingProfile.ReviewerCredentials.CredentialRef) + draft.ReviewerCredentialRef = existingProfile.ReviewerCredentials.Credential.Name draft.ReviewerDisplayName = normalizeOptionalDisplayName(existingProfile.ReviewerCredentials.DisplayName) } } @@ -3244,8 +3241,8 @@ func buildNonInteractiveInitPlan(cmd *cobra.Command, opts *root.Options, flags i } gitRef := flags.gitRef if gitRef == "" { - if previousProfile != nil && previousProfile.Git.CredentialRef != "" { - gitRef = previousProfile.Git.CredentialRef + if previousProfile != nil && previousProfile.Git.Credential.Name != "" { + gitRef = previousProfile.Git.Credential.Name } else { gitRef = defaultGitRef } @@ -3278,8 +3275,8 @@ func buildNonInteractiveInitPlan(cmd *cobra.Command, opts *root.Options, flags i return initPlan{}, exitcode.Usage(fmt.Errorf("reviewer token ingress requires --reviewer-auth-mode %s", config.GitAuthModePAT)) } if reviewerRef == "" { - if previousProfile != nil && previousProfile.ReviewerCredentials != nil && previousProfile.ReviewerCredentials.CredentialRef != "" { - reviewerRef = previousProfile.ReviewerCredentials.CredentialRef + if previousProfile != nil && previousProfile.ReviewerCredentials != nil && previousProfile.ReviewerCredentials.Credential.Name != "" { + reviewerRef = previousProfile.ReviewerCredentials.Credential.Name } else { reviewerRef, err = initCredentialRefFromSeed(profileName + "-reviewer") if err != nil { @@ -3296,8 +3293,8 @@ func buildNonInteractiveInitPlan(cmd *cobra.Command, opts *root.Options, flags i } llmRef := flags.llmRef if flags.llmAuth == string(config.LLMAuthAPIKey) && llmRef == "" { - if previousProfile != nil && previousProfile.LLM.Auth == config.LLMAuthAPIKey && previousProfile.LLM.CredentialRef != "" { - llmRef = previousProfile.LLM.CredentialRef + if previousProfile != nil && previousProfile.LLM.Auth == config.LLMAuthAPIKey && previousProfile.LLM.Credential.Name != "" { + llmRef = previousProfile.LLM.Credential.Name } else { llmRef, err = initCredentialRefFromSeed(profileName + "-llm") if err != nil { @@ -3374,17 +3371,15 @@ func buildNonInteractiveInitPlan(cmd *cobra.Command, opts *root.Options, flags i } profile := config.Profile{ Git: config.GitConfig{ - Host: flags.gitHost, - AuthMode: gitMode, - Credential: initCredentialLocation(gitStore, gitRef), - GitHubApp: initGitHubAppConfigForAuth(gitMode, gitGitHubAppID), - CredentialRef: gitRef, + Host: flags.gitHost, + AuthMode: gitMode, + Credential: initCredentialLocation(gitStore, gitRef), + GitHubApp: initGitHubAppConfigForAuth(gitMode, gitGitHubAppID), }, LLM: config.LLMConfig{ - Provider: config.LLMProvider(flags.llmProvider), - Auth: config.LLMAuth(flags.llmAuth), - Adapter: config.LLMAdapter(flags.llmAdapter), - CredentialRef: llmRef, + Provider: config.LLMProvider(flags.llmProvider), + Auth: config.LLMAuth(flags.llmAuth), + Adapter: config.LLMAdapter(flags.llmAdapter), }, AgentSources: flags.agentSources, ReviewPolicy: config.ReviewPolicy{ @@ -3403,10 +3398,9 @@ func buildNonInteractiveInitPlan(cmd *cobra.Command, opts *root.Options, flags i } if reviewerRequested && !flags.disableReviewer { profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: reviewerMode, - Credential: initCredentialLocation(reviewerStore, reviewerRef), - GitHubApp: initGitHubAppConfigForAuth(reviewerMode, reviewerGitHubAppID), - CredentialRef: reviewerRef, + AuthMode: reviewerMode, + Credential: initCredentialLocation(reviewerStore, reviewerRef), + GitHubApp: initGitHubAppConfigForAuth(reviewerMode, reviewerGitHubAppID), } } if previousProfile != nil { @@ -3735,7 +3729,7 @@ func standaloneRepositoryAccessPlanEntries(cfg config.File, plannedWriteKeys map ref := config.CredentialRef{ Purpose: "git", Store: initCredentialStoreDraftValue(access.Git.Credential.Store), - Ref: strings.TrimSpace(firstNonEmpty(access.Git.Credential.Name, access.Git.CredentialRef)), + Ref: strings.TrimSpace(access.Git.Credential.Name), Mode: string(access.Git.AuthMode), } if ref.Store == "" || ref.Ref == "" { @@ -4231,14 +4225,14 @@ func initLLMConfigForReplacement(profileName string, runtime initLLMRuntimeDraft if llm.Auth != config.LLMAuthAPIKey { return llm, nil } - if strings.TrimSpace(llm.CredentialRef) != "" { + if strings.TrimSpace(llm.Credential.Name) != "" { return llm, nil } ref, err := initCredentialRefFromSeed(profileName + "-llm") if err != nil { return config.LLMConfig{}, exitcode.Usage(err) } - llm.CredentialRef = ref + llm.Credential = initCredentialLocation(llm.Credential.Store, ref) return llm, nil } @@ -4269,17 +4263,16 @@ func initGitScopeDraftFromConfig(git config.GitConfig) initGitScopeDraft { AuthMode: git.AuthMode, GitHubAppID: appID, CredentialStore: initCredentialStoreDraftValue(git.Credential.Store), - CredentialRef: strings.TrimSpace(firstNonEmpty(git.Credential.Name, git.CredentialRef)), + CredentialRef: strings.TrimSpace(git.Credential.Name), } } func (scope initGitScopeDraft) exportConfig(previous *config.GitConfig) config.GitConfig { git := config.GitConfig{ - Host: strings.TrimSpace(scope.Host), - AuthMode: scope.AuthMode, - Credential: initCredentialLocation(scope.CredentialStore, scope.CredentialRef), - GitHubApp: initGitHubAppConfigForAuth(scope.AuthMode, scope.GitHubAppID), - CredentialRef: strings.TrimSpace(scope.CredentialRef), + Host: strings.TrimSpace(scope.Host), + AuthMode: scope.AuthMode, + Credential: initCredentialLocation(scope.CredentialStore, scope.CredentialRef), + GitHubApp: initGitHubAppConfigForAuth(scope.AuthMode, scope.GitHubAppID), } if previous != nil && scope.matchesConfig(*previous) { git.Host = previous.Host @@ -4313,7 +4306,7 @@ func (scope initGitScopeDraft) matchesConfig(previous config.GitConfig) bool { previous.AuthMode == scope.AuthMode && initGitHubAppIDForAuth(previous.AuthMode, previous.GitHubApp) == strings.TrimSpace(scope.GitHubAppID) && initCredentialStoreDraftValue(previous.Credential.Store) == initCredentialStoreDraftValue(scope.CredentialStore) && - strings.TrimSpace(firstNonEmpty(previous.Credential.Name, previous.CredentialRef)) == strings.TrimSpace(scope.CredentialRef) + strings.TrimSpace(previous.Credential.Name) == strings.TrimSpace(scope.CredentialRef) } func buildInitGitScopeInventory(cfg config.File) (map[string]initGitScopeDraft, map[string]string) { @@ -4381,7 +4374,7 @@ func initReviewerEntityDraftFromConfig(profile config.Profile) initReviewerEntit AuthMode: profile.ReviewerCredentials.AuthMode, AppID: initGitHubAppIDForAuth(profile.ReviewerCredentials.AuthMode, profile.ReviewerCredentials.GitHubApp), CredentialStore: initCredentialStoreDraftValue(profile.ReviewerCredentials.Credential.Store), - CredentialRef: strings.TrimSpace(firstNonEmpty(profile.ReviewerCredentials.Credential.Name, profile.ReviewerCredentials.CredentialRef)), + CredentialRef: strings.TrimSpace(profile.ReviewerCredentials.Credential.Name), DisplayName: normalizeOptionalDisplayName(profile.ReviewerCredentials.DisplayName), } return entity @@ -4393,7 +4386,7 @@ func initReviewerEntityDraftFromReviewerEntity(entity config.ReviewerEntity) ini AuthMode: entity.AuthMode, AppID: initGitHubAppIDForAuth(entity.AuthMode, entity.GitHubApp), CredentialStore: initCredentialStoreDraftValue(entity.Credential.Store), - CredentialRef: strings.TrimSpace(firstNonEmpty(entity.Credential.Name, entity.CredentialRef)), + CredentialRef: strings.TrimSpace(entity.Credential.Name), DisplayName: normalizeOptionalDisplayName(entity.DisplayName), } return draft @@ -4404,11 +4397,10 @@ func (entity initReviewerEntityDraft) exportConfig(previous *config.ReviewerCred return nil } reviewer := &config.ReviewerCredentials{ - AuthMode: entity.AuthMode, - Credential: initCredentialLocation(entity.CredentialStore, entity.CredentialRef), - GitHubApp: initGitHubAppConfigForAuth(entity.AuthMode, entity.AppID), - CredentialRef: strings.TrimSpace(entity.CredentialRef), - DisplayName: normalizeOptionalDisplayName(entity.DisplayName), + AuthMode: entity.AuthMode, + Credential: initCredentialLocation(entity.CredentialStore, entity.CredentialRef), + GitHubApp: initGitHubAppConfigForAuth(entity.AuthMode, entity.AppID), + DisplayName: normalizeOptionalDisplayName(entity.DisplayName), } if previous != nil && entity.matchesConfig(*previous) { reviewer.IdentityCache = previous.IdentityCache @@ -4418,12 +4410,11 @@ func (entity initReviewerEntityDraft) exportConfig(previous *config.ReviewerCred func (entity initReviewerEntityDraft) exportReviewerEntityConfig(previous *config.ReviewerEntity, host string) config.ReviewerEntity { reviewer := config.ReviewerEntity{ - Host: strings.TrimSpace(host), - AuthMode: entity.AuthMode, - Credential: initCredentialLocation(entity.CredentialStore, entity.CredentialRef), - GitHubApp: initGitHubAppConfigForAuth(entity.AuthMode, entity.AppID), - CredentialRef: strings.TrimSpace(entity.CredentialRef), - DisplayName: normalizeOptionalDisplayName(entity.DisplayName), + Host: strings.TrimSpace(host), + AuthMode: entity.AuthMode, + Credential: initCredentialLocation(entity.CredentialStore, entity.CredentialRef), + GitHubApp: initGitHubAppConfigForAuth(entity.AuthMode, entity.AppID), + DisplayName: normalizeOptionalDisplayName(entity.DisplayName), } if previous != nil { if reviewer.Host == "" { @@ -4469,7 +4460,7 @@ func (entity initReviewerEntityDraft) matchesConfig(previous config.ReviewerCred previous.AuthMode == entity.AuthMode && initGitHubAppIDForAuth(previous.AuthMode, previous.GitHubApp) == strings.TrimSpace(entity.AppID) && initCredentialStoreDraftValue(previous.Credential.Store) == initCredentialStoreDraftValue(entity.CredentialStore) && - strings.TrimSpace(firstNonEmpty(previous.Credential.Name, previous.CredentialRef)) == strings.TrimSpace(entity.CredentialRef) + strings.TrimSpace(previous.Credential.Name) == strings.TrimSpace(entity.CredentialRef) } func (entity initReviewerEntityDraft) matchesReviewerEntityConfig(previous config.ReviewerEntity) bool { @@ -4477,7 +4468,7 @@ func (entity initReviewerEntityDraft) matchesReviewerEntityConfig(previous confi previous.AuthMode == entity.AuthMode && initGitHubAppIDForAuth(previous.AuthMode, previous.GitHubApp) == strings.TrimSpace(entity.AppID) && initCredentialStoreDraftValue(previous.Credential.Store) == initCredentialStoreDraftValue(entity.CredentialStore) && - strings.TrimSpace(firstNonEmpty(previous.Credential.Name, previous.CredentialRef)) == strings.TrimSpace(entity.CredentialRef) + strings.TrimSpace(previous.Credential.Name) == strings.TrimSpace(entity.CredentialRef) } func initGitHubAppConfigForAuth(authMode config.GitAuthMode, appID string) *config.GitHubAppConfig { @@ -4557,7 +4548,7 @@ func initLLMRuntimeDraftFromConfig(llm config.LLMConfig) initLLMRuntimeDraft { Auth: llm.Auth, Adapter: llm.Adapter, CredentialStore: initCredentialStoreDraftValue(llm.Credential.Store), - CredentialRef: strings.TrimSpace(firstNonEmpty(llm.Credential.Name, llm.CredentialRef)), + CredentialRef: strings.TrimSpace(llm.Credential.Name), ModelMap: copyModelMap(llm.ModelMap), ReviewerModelTier: llm.ReviewerModelTier, } @@ -4581,7 +4572,6 @@ func (runtime initLLMRuntimeDraft) exportConfig() config.LLMConfig { } if runtime.Auth == config.LLMAuthAPIKey { llm.Credential = initCredentialLocation(runtime.CredentialStore, runtime.CredentialRef) - llm.CredentialRef = strings.TrimSpace(runtime.CredentialRef) } return llm } @@ -4828,11 +4818,11 @@ func synthesizeInteractiveProfile(flags initOptions, profileName string, previou profile.Git.Host = strings.TrimSpace(draft.GitHost) profile.Git.AuthMode = config.GitAuthMode(draft.GitAuth) profile.Git.GitHubApp = initGitHubAppConfigForAuth(profile.Git.AuthMode, draft.GitHubAppID) - profile.Git.CredentialRef = strings.TrimSpace(draft.GitCredentialRef) - if profile.Git.CredentialRef == "" { - profile.Git.CredentialRef = defaultGitRef + gitRef := strings.TrimSpace(draft.GitCredentialRef) + if gitRef == "" { + gitRef = defaultGitRef } - profile.Git.Credential = initCredentialLocation(draft.GitCredentialStore, profile.Git.CredentialRef) + profile.Git.Credential = initCredentialLocation(draft.GitCredentialStore, gitRef) // Changing the selected Git scope means any cached resolved identity may belong to the wrong host/auth pair. if previousProfile != nil && !initGitScopeDraftFromConfig(profile.Git).matchesConfig(previousProfile.Git) { profile.Git.IdentityCache = "" @@ -4848,11 +4838,10 @@ func synthesizeInteractiveProfile(flags initOptions, profileName string, previou } } reviewer := config.ReviewerCredentials{ - AuthMode: config.GitAuthMode(draft.ReviewerAuth), - Credential: initCredentialLocation(draft.ReviewerCredentialStore, reviewerRef), - GitHubApp: initGitHubAppConfigForAuth(config.GitAuthMode(draft.ReviewerAuth), draft.ReviewerGitHubAppID), - CredentialRef: reviewerRef, - DisplayName: normalizeOptionalDisplayName(draft.ReviewerDisplayName), + AuthMode: config.GitAuthMode(draft.ReviewerAuth), + Credential: initCredentialLocation(draft.ReviewerCredentialStore, reviewerRef), + GitHubApp: initGitHubAppConfigForAuth(config.GitAuthMode(draft.ReviewerAuth), draft.ReviewerGitHubAppID), + DisplayName: normalizeOptionalDisplayName(draft.ReviewerDisplayName), } // Changing reviewer credentials can invalidate the cached reviewer identity even when the profile name stays the same. if previousProfile != nil && previousProfile.ReviewerCredentials != nil && initReviewerEntityDraftFromConfig(config.Profile{ReviewerCredentials: &reviewer}).matchesConfig(*previousProfile.ReviewerCredentials) { @@ -4876,10 +4865,8 @@ func synthesizeInteractiveProfile(flags initOptions, profileName string, previou return config.Profile{}, exitcode.Usage(err) } } - profile.LLM.CredentialRef = llmRef profile.LLM.Credential = initCredentialLocationIfName(draft.LLMCredentialStore, llmRef) } else { - profile.LLM.CredentialRef = "" profile.LLM.Credential = config.CredentialLocation{} } if draft.ModelMapSet { @@ -4895,7 +4882,6 @@ func synthesizeInteractiveProfile(flags initOptions, profileName string, previou if draft.ReviewPolicySet { profile.ReviewPolicy = draft.ReviewPolicy } - profile.SecretsStore = strings.TrimSpace(draft.SecretsStore) return profile, nil } @@ -5063,13 +5049,12 @@ func reviewerEntityConfigFromProfile(profile config.Profile) config.ReviewerEnti if reviewer == nil { return config.ReviewerEntity{} } - ref := strings.TrimSpace(firstNonEmpty(reviewer.Credential.Name, reviewer.CredentialRef)) + ref := strings.TrimSpace(reviewer.Credential.Name) return normalizeInitReviewerEntityConfig(config.ReviewerEntity{ Host: profile.Git.Host, AuthMode: reviewer.AuthMode, Credential: initCredentialLocation(reviewer.Credential.Store, ref), GitHubApp: initGitHubAppConfigForAuth(reviewer.AuthMode, initGitHubAppIDForAuth(reviewer.AuthMode, reviewer.GitHubApp)), - CredentialRef: ref, DisplayName: normalizeOptionalDisplayName(reviewer.DisplayName), IdentityCache: reviewer.IdentityCache, }) @@ -5343,34 +5328,7 @@ func collectInteractiveInitKeyringBackendConfig(opts *root.Options, deps initDep } func validateInteractiveInitConfig(cfg config.File) error { - err := config.Validate(cfg) - if err == nil { - return nil - } - if !errors.Is(err, config.ErrSecretsStoreNotFound) { - return err - } - recovered := cloneInitConfigFile(cfg) - hadBrokenSecretsStore := false - for name, profile := range recovered.Profiles { - selection := strings.TrimSpace(profile.SecretsStore) - if selection == "" || selection == config.LocalOSCredentialStoreID { - continue - } - if _, ok := recovered.Secrets.Stores[selection]; ok { - continue - } - profile.SecretsStore = "" - recovered.Profiles[name] = profile - hadBrokenSecretsStore = true - } - if !hadBrokenSecretsStore { - return err - } - if recoveredErr := config.Validate(recovered); recoveredErr != nil { - return recoveredErr - } - return nil + return config.Validate(cfg) } func validateInteractiveInitGlobalConfig(cfg config.File) error { @@ -6600,7 +6558,7 @@ func applyInitPlan(opts *root.Options, flags initOptions, deps initDeps, plan in if flags.overwrite { return exitcode.Usage(fmt.Errorf("--overwrite with api_key LLM auth requires --llm-api-key-stdin or --llm-api-key-from-env")) } - parsed, err := credentials.ParseRef(plan.profile.LLM.CredentialRef) + parsed, err := credentials.ParseRef(plan.profile.LLM.Credential.Name) if err != nil { return exitcode.Usage(err) } diff --git a/internal/cmd/initcmd/initcmd_test.go b/internal/cmd/initcmd/initcmd_test.go index ade66fc..db05bb1 100644 --- a/internal/cmd/initcmd/initcmd_test.go +++ b/internal/cmd/initcmd/initcmd_test.go @@ -71,10 +71,10 @@ func TestInitNonInteractiveNormalizesProfileNameCredentialRefs(t *testing.T) { t.Fatalf("Load config: %v", err) } profile := cfg.Profiles["bad.profile"] - if profile.Git.CredentialRef != "codereview/bad_x2eprofile" { - t.Fatalf("Git.CredentialRef = %q, want normalized profile ref", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/bad_x2eprofile" { + t.Fatalf("Git.CredentialRef = %q, want normalized profile ref", profile.Git.Credential.Name) } - if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.CredentialRef != "codereview/bad_x2eprofile-reviewer" { + if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.Credential.Name != "codereview/bad_x2eprofile-reviewer" { t.Fatalf("ReviewerCredentials = %#v, want normalized reviewer ref", profile.ReviewerCredentials) } } @@ -103,7 +103,7 @@ func TestInitNonInteractiveWritesReviewerCredential(t *testing.T) { t.Fatal("reviewer credentials missing") return } - if reviewer.AuthMode != config.GitAuthModePAT || reviewer.CredentialRef != "codereview/default-reviewer" { + if reviewer.AuthMode != config.GitAuthModePAT || reviewer.Credential.Name != "codereview/default-reviewer" { t.Fatalf("reviewer credentials = %#v, want pat codereview/default-reviewer", reviewer) } if reviewer.Credential.Store != config.LocalOSCredentialStoreID || reviewer.Credential.Name != "codereview/default-reviewer" { @@ -137,14 +137,14 @@ func TestInitNonInteractiveWritesGitHubAppReviewerConfigOnly(t *testing.T) { t.Fatalf("profile reviewer github_app_installation = %#v, want discover_from_repository", profile.Reviewer.GitHubAppInstallation) } entity := cfg.ReviewerEntities["default-reviewer"] - if entity.AuthMode != config.GitAuthModeGitHubApp || entity.CredentialRef != "codereview/default-reviewer" { + if entity.AuthMode != config.GitAuthModeGitHubApp || entity.Credential.Name != "codereview/default-reviewer" { t.Fatalf("reviewer entity = %#v, want github_app codereview/default-reviewer", entity) } if entity.GitHubApp == nil || entity.GitHubApp.AppID != "12345" { t.Fatalf("reviewer entity github_app = %#v, want app_id 12345", entity.GitHubApp) } reviewer := cfg.Profiles["default"].ReviewerCredentials - if reviewer == nil || reviewer.AuthMode != config.GitAuthModeGitHubApp || reviewer.CredentialRef != "codereview/default-reviewer" { + if reviewer == nil || reviewer.AuthMode != config.GitAuthModeGitHubApp || reviewer.Credential.Name != "codereview/default-reviewer" { t.Fatalf("reviewer credentials = %#v, want github_app codereview/default-reviewer", reviewer) } if reviewer.GitHubApp == nil || reviewer.GitHubApp.AppID != "12345" { @@ -220,7 +220,7 @@ func TestInitNonInteractiveWritesCustomReviewerCredentialFromStdin(t *testing.T) t.Fatalf("Load config: %v", err) } reviewer := cfg.Profiles["work"].ReviewerCredentials - if reviewer == nil || reviewer.CredentialRef != "codereview/review-bot" { + if reviewer == nil || reviewer.Credential.Name != "codereview/review-bot" { t.Fatalf("reviewer credentials = %#v, want custom codereview/review-bot", reviewer) } assertFakeStored(t, store, "work", credentials.GitTokenKey, "git-token") @@ -246,7 +246,7 @@ func TestInitNonInteractiveDerivesReviewerRefFromStdinForProfile(t *testing.T) { t.Fatalf("Load config: %v", err) } reviewer := cfg.Profiles["work"].ReviewerCredentials - if reviewer == nil || reviewer.CredentialRef != "codereview/work-reviewer" { + if reviewer == nil || reviewer.Credential.Name != "codereview/work-reviewer" { t.Fatalf("reviewer credentials = %#v, want derived codereview/work-reviewer", reviewer) } assertFakeStored(t, store, "work", credentials.GitTokenKey, "git-token") @@ -298,7 +298,7 @@ func TestInitNonInteractiveWritesProviderSpecificAPIKeySecret(t *testing.T) { t.Fatalf("Load config: %v", err) } profile := cfg.Profiles["default"] - if profile.LLM.Provider != tt.provider || profile.LLM.CredentialRef != "codereview/default-llm" { + if profile.LLM.Provider != tt.provider || profile.LLM.Credential.Name != "codereview/default-llm" { t.Fatalf("LLM config = %#v, want provider %s default LLM ref", profile.LLM, tt.provider) } assertFakeBundleKeys(t, store, "default", []string{credentials.GitTokenKey}) @@ -332,7 +332,7 @@ func TestInitNonInteractiveWritesPiRPCProfile(t *testing.T) { if profile.LLM.Provider != config.LLMProviderPi || profile.LLM.Auth != config.LLMAuthSubscription || profile.LLM.Adapter != config.LLMAdapterPiRPC || - profile.LLM.CredentialRef != "" { + profile.LLM.Credential.Name != "" { t.Fatalf("LLM config = %#v, want pi subscription pi_rpc without credential ref", profile.LLM) } assertFakeBundleKeys(t, store, "default", []string{credentials.GitTokenKey}) @@ -361,9 +361,9 @@ func TestPlanInitCredentials(t *testing.T) { t.Run("reviewer github app default ref includes bundle keys", func(t *testing.T) { desired := basicProfile("work") desired.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } entries, err := planInitCredentials(nil, desired, nil) if err != nil { @@ -395,10 +395,10 @@ func TestPlanInitCredentials(t *testing.T) { t.Run(tt.name, func(t *testing.T) { desired := basicProfile("work") desired.LLM = config.LLMConfig{ - Provider: tt.provider, - Auth: config.LLMAuthAPIKey, - Adapter: tt.adapter, - CredentialRef: "codereview/work-llm", + Provider: tt.provider, + Auth: config.LLMAuthAPIKey, + Adapter: tt.adapter, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, } entries, err := planInitCredentials(nil, desired, nil) if err != nil { @@ -424,19 +424,19 @@ func TestPlanInitCredentials(t *testing.T) { Profiles: map[string]config.Profile{ "work": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-reviewer"}, }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/custom-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-llm"}, }, }, }, @@ -478,8 +478,8 @@ func TestPlanInitCredentials(t *testing.T) { t.Run("optional refs can clear", func(t *testing.T) { previous := apiKeyProfile("work", config.LLMProviderAnthropic) previous.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } desired := basicProfile("work") entries, err := planInitCredentials(&previous, desired, nil) @@ -641,15 +641,15 @@ func TestBuildInteractiveInitSessionPlanUsesOriginalProfileForRenamedTouchedProf Profiles: map[string]config.Profile{ "work": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "Old label", }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -737,7 +737,7 @@ func TestInitReviewerConfigOnlyCarriesBackendIntoCredentialHint(t *testing.T) { t.Fatalf("Load config: %v", err) } reviewer := cfg.Profiles["default"].ReviewerCredentials - if reviewer == nil || reviewer.CredentialRef != "codereview/default-reviewer" { + if reviewer == nil || reviewer.Credential.Name != "codereview/default-reviewer" { t.Fatalf("reviewer credentials = %#v, want codereview/default-reviewer", reviewer) } if got := errOut.String(); !strings.Contains(got, "cr set-credential --store local-os --name codereview/default-reviewer --key git_token --stdin") { @@ -805,22 +805,19 @@ func TestInitReplaceProfilePreservesExistingCredentialRefsByDefault(t *testing.T Profiles: map[string]config.Profile{ "work": profileWithCredentialStore(config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-git"}, - CredentialRef: "codereview/custom-git", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-git"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-reviewer"}, - CredentialRef: "codereview/custom-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-reviewer"}, }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-llm"}, - CredentialRef: "codereview/custom-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-llm"}, }, }, testFileCredentialStoreID), }, @@ -842,20 +839,20 @@ func TestInitReplaceProfilePreservesExistingCredentialRefsByDefault(t *testing.T t.Fatalf("Load config: %v", err) } profile := got.Profiles["work"] - if profile.Git.CredentialRef != "codereview/custom-git" { - t.Fatalf("git ref = %q, want preserved custom-git", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/custom-git" { + t.Fatalf("git ref = %q, want preserved custom-git", profile.Git.Credential.Name) } if profile.Git.Credential.Store != testFileCredentialStoreID || profile.Git.Credential.Name != "codereview/custom-git" { t.Fatalf("git credential = %#v, want preserved test-file custom-git", profile.Git.Credential) } - if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.CredentialRef != "codereview/custom-reviewer" { + if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.Credential.Name != "codereview/custom-reviewer" { t.Fatalf("reviewer = %#v, want preserved custom-reviewer", profile.ReviewerCredentials) } if profile.ReviewerCredentials.Credential.Store != testFileCredentialStoreID || profile.ReviewerCredentials.Credential.Name != "codereview/custom-reviewer" { t.Fatalf("reviewer credential = %#v, want preserved test-file custom-reviewer", profile.ReviewerCredentials.Credential) } - if profile.LLM.CredentialRef != "codereview/custom-llm" { - t.Fatalf("llm ref = %q, want preserved custom-llm", profile.LLM.CredentialRef) + if profile.LLM.Credential.Name != "codereview/custom-llm" { + t.Fatalf("llm ref = %q, want preserved custom-llm", profile.LLM.Credential.Name) } if profile.LLM.Credential.Store != testFileCredentialStoreID || profile.LLM.Credential.Name != "codereview/custom-llm" { t.Fatalf("llm credential = %#v, want preserved test-file custom-llm", profile.LLM.Credential) @@ -885,8 +882,8 @@ func TestInitReplaceProfileRefOverwriteEmitsFollowUpHint(t *testing.T) { if err != nil { t.Fatalf("Load config: %v", err) } - if got.Profiles["work"].Git.CredentialRef != "codereview/rotated-git" { - t.Fatalf("git ref = %q, want rotated-git", got.Profiles["work"].Git.CredentialRef) + if got.Profiles["work"].Git.Credential.Name != "codereview/rotated-git" { + t.Fatalf("git ref = %q, want rotated-git", got.Profiles["work"].Git.Credential.Name) } if !strings.Contains(errOut.String(), "set-credential --store local-os --name codereview/rotated-git --key git_token --stdin") { t.Fatalf("stderr = %q, want overwrite-ref follow-up hint", errOut.String()) @@ -1058,7 +1055,7 @@ func TestInitDisableReviewerClearsReviewerCredentials(t *testing.T) { existing.Git.IdentityCache = "git-cache" existing.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, IdentityCache: "reviewer-cache", } existing.LLM.ModelMap = config.ModelMap{ @@ -1109,7 +1106,7 @@ func TestInitReplaceProfilePreservesReviewerDisplayNameWhenReviewerIdentityUncha existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, DisplayName: "Work reviewer bot", IdentityCache: "reviewer-cache", } @@ -1175,7 +1172,7 @@ func TestInitLLMReviewerModelTierFlags(t *testing.T) { existing.Git.IdentityCache = "git-cache" existing.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, IdentityCache: "reviewer-cache", } existing.LLM.ModelMap = config.ModelMap{ @@ -1512,7 +1509,6 @@ func TestInitGitScopeDraftRoundTripPreservesIdentityCacheFromPreviousProfile(t * AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work", IdentityCache: "rianjs-work", } @@ -1530,7 +1526,6 @@ func TestInitGitScopeDraftExportClearsIdentityCacheWhenShapeChanges(t *testing.T AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work", IdentityCache: "rianjs-work", } @@ -1572,7 +1567,6 @@ func TestInitReviewerEntityDraftRoundTripVariants(t *testing.T) { p.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-bot", } return p @@ -1580,13 +1574,11 @@ func TestInitReviewerEntityDraftRoundTripVariants(t *testing.T) { previous: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-bot", }, want: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-bot", }, kind: initReviewerEntityKindPAT, @@ -1599,7 +1591,6 @@ func TestInitReviewerEntityDraftRoundTripVariants(t *testing.T) { AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-app", } return p @@ -1608,14 +1599,12 @@ func TestInitReviewerEntityDraftRoundTripVariants(t *testing.T) { AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-app", }, want: &config.ReviewerCredentials{ AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-app", }, kind: initReviewerEntityKindGitHubApp, @@ -1640,7 +1629,6 @@ func TestInitReviewerEntityDraftExportClearsIdentityCacheWhenShapeChanges(t *tes AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "review-app", } entity := initReviewerEntityDraft{ @@ -1664,11 +1652,10 @@ func TestInitReviewerEntityDraftExportClearsIdentityCacheWhenShapeChanges(t *tes func TestBuildInteractiveInitWorkspaceClearsReviewerDisplayNameWhenDraftLeavesItBlank(t *testing.T) { existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + DisplayName: "Old label", } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -1725,8 +1712,8 @@ func TestBuildInitGitScopeInventoryUsesProjectedRepositoryAccessNames(t *testing work := basicProfile("work") home.Git.Host = "github.com" work.Git.Host = "https://github.com/" - home.Git.CredentialRef = "codereview/home" - work.Git.CredentialRef = "codereview/work" + home.Git.Credential.Name = "codereview/home" + work.Git.Credential.Name = "codereview/work" cfg := config.File{ Profiles: map[string]config.Profile{ "home": home, @@ -1755,12 +1742,12 @@ func TestBuildInitReviewerEntityInventoryVariantsAndDeduping(t *testing.T) { work := basicProfile("work") bot := basicProfile("bot") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } bot.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -1787,12 +1774,12 @@ func TestBuildInitReviewerEntityInventoryAssignsStableSuffixOnNameCollision(t *t home := basicProfile("home") work := basicProfile("work") home.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-reviewer"}, } work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -1921,14 +1908,14 @@ func TestBuildInitReviewerEntityInventoryConflictingSharedDisplayNamesFallBackTo home := basicProfile("home") work := basicProfile("work") home.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", - DisplayName: "OC Collective bot", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, + DisplayName: "OC Collective bot", } work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", - DisplayName: "Work reviewer bot", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, + DisplayName: "Work reviewer bot", } entities, profileEntityNames := buildInitReviewerEntityInventory(config.File{ @@ -2006,15 +1993,15 @@ func TestBuildInitReviewerEntityInventorySharedDisplayNameWinsWhenOnlyOneProfile home := basicProfile("home") work := basicProfile("work") home.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", - DisplayName: "OC Collective bot", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, + DisplayName: "OC Collective bot", } work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, } entities, profileEntityNames := buildInitReviewerEntityInventory(config.File{ @@ -2048,11 +2035,11 @@ func TestEditInteractiveInitReviewerEntityStepUpdatesConfiguredEntity(t *testing cfg := config.File{ ReviewerEntities: map[string]config.ReviewerEntity{ "oc-collective-bot": { - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", - DisplayName: "Old label", + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, + DisplayName: "Old label", }, }, Profiles: map[string]config.Profile{ @@ -2093,7 +2080,7 @@ func TestEditInteractiveInitReviewerEntityStepUpdatesConfiguredEntity(t *testing if got, want := entity.AuthMode, config.GitAuthModeGitHubApp; got != want { t.Fatalf("entity auth mode = %q, want %q", got, want) } - if got, want := entity.CredentialRef, "codereview/open-cli-collective-rianjs-bot-renamed"; got != want { + if got, want := entity.Credential.Name, "codereview/open-cli-collective-rianjs-bot-renamed"; got != want { t.Fatalf("entity credential ref = %q, want %q", got, want) } if got, want := entity.DisplayName, "OC Collective bot"; got != want { @@ -2110,7 +2097,7 @@ func TestEditInteractiveInitReviewerEntityStepUpdatesConfiguredEntity(t *testing if got, want := profile.ReviewerCredentials.AuthMode, config.GitAuthModeGitHubApp; got != want { t.Fatalf("%s auth mode = %q, want %q", profileName, got, want) } - if got, want := profile.ReviewerCredentials.CredentialRef, "codereview/open-cli-collective-rianjs-bot-renamed"; got != want { + if got, want := profile.ReviewerCredentials.Credential.Name, "codereview/open-cli-collective-rianjs-bot-renamed"; got != want { t.Fatalf("%s credential ref = %q, want %q", profileName, got, want) } if got, want := profile.ReviewerCredentials.DisplayName, "OC Collective bot"; got != want { @@ -2158,7 +2145,7 @@ func TestEditInteractiveInitReviewerEntityStepDefaultsBlankRefToStandaloneEntity t.Fatal("stayInCategory = true, want focused reviewer flow to return to main menu after stage") } entity := next.cfg.ReviewerEntities["reviewer-github-app"] - if got, want := entity.CredentialRef, "codereview/reviewer-github-app"; got != want { + if got, want := entity.Credential.Name, "codereview/reviewer-github-app"; got != want { t.Fatalf("entity credential ref = %q, want %q", got, want) } if got, want := entity.DisplayName, "OC Collective bot"; got != want { @@ -2174,11 +2161,11 @@ func TestEditInteractiveInitReviewerEntityStepSelectingFallbackDoesNotPropagateS cfg := config.File{ ReviewerEntities: map[string]config.ReviewerEntity{ "oc-collective-bot": { - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", - DisplayName: "Old label", + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, + DisplayName: "Old label", }, }, Profiles: map[string]config.Profile{ @@ -2224,7 +2211,7 @@ func TestEditInteractiveInitReviewerEntityStepSelectingFallbackDoesNotPropagateS if got, want := workProfile.ReviewerCredentials.DisplayName, "Old label"; got != want { t.Fatalf("work display name = %q, want %q", got, want) } - if got, want := workProfile.ReviewerCredentials.CredentialRef, "codereview/open-cli-collective-rianjs-bot"; got != want { + if got, want := workProfile.ReviewerCredentials.Credential.Name, "codereview/open-cli-collective-rianjs-bot"; got != want { t.Fatalf("work credential ref = %q, want %q", got, want) } } @@ -2275,7 +2262,7 @@ func TestInitReviewerEntityInventoryRowsUseExplicitGitAccountFallbackLabels(t *t git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, IdentityCache: "rianjs", }, wantText: "Post as rianjs (GitHub PAT)", @@ -2285,7 +2272,7 @@ func TestInitReviewerEntityInventoryRowsUseExplicitGitAccountFallbackLabels(t *t git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/home-app", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-app"}, IdentityCache: "review-bot", }, wantText: "Post as review-bot (GitHub App)", @@ -2293,18 +2280,18 @@ func TestInitReviewerEntityInventoryRowsUseExplicitGitAccountFallbackLabels(t *t { name: "unknown PAT identity", git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, wantText: "Post using this profile's Git account (GitHub PAT)", }, { name: "unknown GitHub App identity", git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/home-app", + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-app"}, }, wantText: "Post using this profile's Git account (GitHub App)", }, @@ -2344,7 +2331,7 @@ func TestProfileEditorReviewerEntityFallbackLabelUsesExplicitGitAccountFallbackL Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, IdentityCache: "rianjs", }, }, @@ -2359,9 +2346,9 @@ func TestProfileEditorReviewerEntityFallbackLabelUsesExplicitGitAccountFallbackL }, existing: &config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/home-app", + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-app"}, }, }, wantText: "Post using this profile's Git account (GitHub App)", @@ -2401,18 +2388,18 @@ func TestSharedGitScopeAndReviewerEntityDoNotDriftIdentityCacheAcrossProfiles(t work.Git.Host = "https://github.mycompany.com/" home.Git.AuthMode = config.GitAuthModeGitHubApp work.Git.AuthMode = config.GitAuthModeGitHubApp - home.Git.CredentialRef = "codereview/shared-git" - work.Git.CredentialRef = "codereview/shared-git" + home.Git.Credential.Name = "codereview/shared-git" + work.Git.Credential.Name = "codereview/shared-git" home.Git.IdentityCache = "home-cache" work.Git.IdentityCache = "work-cache" home.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/shared-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, IdentityCache: "home-reviewer-cache", } work.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/shared-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, IdentityCache: "work-reviewer-cache", } cfg := config.File{ @@ -2480,22 +2467,20 @@ func TestInitLLMRuntimeDraftFromConfigRecognizesKnownPresets(t *testing.T) { { name: "anthropic api key", llm: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, preset: initLLMRuntimePresetAnthropicAPIKey, }, { name: "openai api key", llm: config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, preset: initLLMRuntimePresetOpenAIAPIKey, }, @@ -2707,13 +2692,13 @@ func TestDeleteInteractiveInitProfilePrunesRoutesAndUndoRestores(t *testing.T) { func TestDeleteInteractiveInitReviewerEntityClearsAffectedProfilesAndUndoSkipsReeditedProfiles(t *testing.T) { work := basicProfile("work") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } home := basicProfile("home") home.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } bot := basicProfile("bot") session := initSessionDraft{ @@ -2745,8 +2730,8 @@ func TestDeleteInteractiveInitReviewerEntityClearsAffectedProfilesAndUndoSkipsRe editedHome := next.cfg.Profiles["home"] editedHome.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-reviewer"}, } next.cfg.Profiles["home"] = editedHome @@ -2757,7 +2742,7 @@ func TestDeleteInteractiveInitReviewerEntityClearsAffectedProfilesAndUndoSkipsRe if restored.cfg.Profiles["work"].ReviewerCredentials == nil { t.Fatal("work reviewer credentials = nil, want restored shared reviewer") } - if got := restored.cfg.Profiles["home"].ReviewerCredentials; got == nil || got.CredentialRef != "codereview/home-reviewer" { + if got := restored.cfg.Profiles["home"].ReviewerCredentials; got == nil || got.Credential.Name != "codereview/home-reviewer" { t.Fatalf("home reviewer credentials after undo = %#v, want preserved re-edit", got) } } @@ -2787,10 +2772,11 @@ func TestDeleteInteractiveInitLLMRuntimeRebindsAffectedProfilesAndUndoSkipsReedi _, profileRuntimeNames := buildInitLLMRuntimeInventory(session.cfg) runtimeName := profileRuntimeNames["work"] replacement := initLLMRuntimeDraft{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/shared-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + CredentialStore: config.LocalOSCredentialStoreID, + CredentialRef: "codereview/shared-llm", } next, err := deleteInteractiveInitLLMRuntime(session, runtimeName, replacement) @@ -2799,7 +2785,7 @@ func TestDeleteInteractiveInitLLMRuntimeRebindsAffectedProfilesAndUndoSkipsReedi } for _, profileName := range []string{"work", "home"} { llm := next.cfg.Profiles[profileName].LLM - if llm.Provider != config.LLMProviderOpenAI || llm.Auth != config.LLMAuthAPIKey || llm.Adapter != config.LLMAdapterOpenAIAPI || llm.CredentialRef != "codereview/shared-llm" { + if llm.Provider != config.LLMProviderOpenAI || llm.Auth != config.LLMAuthAPIKey || llm.Adapter != config.LLMAdapterOpenAIAPI || llm.Credential.Name != "codereview/shared-llm" { t.Fatalf("%s llm after delete = %#v, want shared openai api-key replacement", profileName, llm) } } @@ -2872,13 +2858,11 @@ func TestBuildInteractiveInitWorkspaceImportsGitScopeAndReviewerEntityInventory( existing.Git.AuthMode = config.GitAuthModeGitHubApp existing.Git.GitHubApp = &config.GitHubAppConfig{AppID: "12345"} existing.Git.Credential = config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/office-git"} - existing.Git.CredentialRef = "codereview/office-git" existing.Git.IdentityCache = "git-cache" existing.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModeGitHubApp, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/office-reviewer"}, GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/office-reviewer", IdentityCache: "reviewer-cache", } cfg := config.File{ @@ -2952,7 +2936,7 @@ func TestInitInteractivePromptDrivenFlowStillExportsReviewerNilAfterDraftInvento t.Fatalf("runInitWithDeps: %v", err) } profile := savedCfg.Profiles["office"] - if profile.Git.Host != "github.mycompany.com" || profile.Git.AuthMode != config.GitAuthModePAT || profile.Git.CredentialRef != "codereview/office-git" { + if profile.Git.Host != "github.mycompany.com" || profile.Git.AuthMode != config.GitAuthModePAT || profile.Git.Credential.Name != "codereview/office-git" { t.Fatalf("git profile = %#v, want PAT office git scope", profile.Git) } if profile.ReviewerCredentials != nil { @@ -3028,7 +3012,7 @@ func TestInitInteractivePromptDrivenFlowStillExportsSeparateReviewerAfterDraftIn if profile.ReviewerCredentials == nil { t.Fatal("reviewer credentials = nil, want separate reviewer preserved") } - if profile.ReviewerCredentials.AuthMode != config.GitAuthModeGitHubApp || profile.ReviewerCredentials.CredentialRef != "codereview/office-reviewer" { + if profile.ReviewerCredentials.AuthMode != config.GitAuthModeGitHubApp || profile.ReviewerCredentials.Credential.Name != "codereview/office-reviewer" { t.Fatalf("reviewer credentials = %#v, want github_app office reviewer", profile.ReviewerCredentials) } } @@ -3120,7 +3104,7 @@ func TestCollectInteractiveInitSecretsPassesDestinationToSharedCredentialPrompts }, }, } - resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{SecretsStore: "team-vault"}) + resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{Git: config.GitConfig{Credential: config.CredentialLocation{Store: "team-vault"}}}) if err != nil { t.Fatalf("ResolveSecretsStoreForProfile: %v", err) } @@ -3517,8 +3501,8 @@ func testInitMultiKeySecretWorkspace() initWorkspaceDraft { func TestPlanInitCredentialsClearsOptionalRefsInStableOrder(t *testing.T) { previous := apiKeyProfile("work", config.LLMProviderAnthropic) previous.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } desired := basicProfile("work") @@ -3618,9 +3602,9 @@ func TestInitCreateProfileSeedNameUsesNextAvailableGeneratedName(t *testing.T) { func TestProfileEditorSelectionPreservesSelectedReviewerEntityLabel(t *testing.T) { existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/work-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "Old label", } draft := seedInteractiveInitDraft("work", "work", &existing) @@ -3728,8 +3712,8 @@ func TestHuhInitReviewerEntityPrompterAccessibleKeepsFallbackSelectedInMixedInve home.Git.IdentityCache = "rianjs" work := basicProfile("work") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -3781,8 +3765,8 @@ func TestHuhInitReviewerEntityPrompterAccessibleConfiguredReviewerRoundTripsInMi home := basicProfile("home") work := basicProfile("work") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-work-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -4585,10 +4569,10 @@ func TestInitLLMRuntimeLinearEditorChangingSelectionResetsDeleteMode(t *testing. func TestHuhInitLLMRuntimePrompterDefaultPreservesConfiguredAPIKeyRuntimeRef(t *testing.T) { existing := basicProfile("work") existing.LLM = config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/custom-openai", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-openai"}, } cfg := config.File{ Profiles: map[string]config.Profile{"work": existing}, @@ -4695,9 +4679,9 @@ func TestHuhInitReviewerEntityPrompterNewTemplateDoesNotInheritCustomSecretLocat t.Setenv("TERM", "dumb") existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-work-reviewer", - DisplayName: "Old reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work-reviewer"}, + DisplayName: "Old reviewer", } var stderr bytes.Buffer prompter := huhInitReviewerEntityPrompter{ @@ -4801,9 +4785,9 @@ func TestHuhInitReviewerEntityPrompterExistingReviewerCustomSecretLocationPersis t.Setenv("TERM", "dumb") existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-work-reviewer", - DisplayName: "Old reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work-reviewer"}, + DisplayName: "Old reviewer", } draft := seedInteractiveInitDraft("work", "work", &existing) var stderr bytes.Buffer @@ -4837,9 +4821,9 @@ func TestHuhInitReviewerEntityPrompterExistingReviewerFallbackSeedDoesNotPersist t.Setenv("TERM", "dumb") existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/open-cli-collective-rianjs-bot", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/open-cli-collective-rianjs-bot"}, } draft := seedInteractiveInitDraft("work", "work", &existing) var stderr bytes.Buffer @@ -4864,10 +4848,10 @@ func TestHuhInitReviewerEntityPrompterAccessibleShowsSeededDisplayNamePrompt(t * t.Setenv("TERM", "dumb") existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "Old label", } draft := seedInteractiveInitDraft("work", "work", &existing) draft.ReviewerEnabled = true @@ -4900,9 +4884,9 @@ func TestHuhInitReviewerEntityPrompterExistingReviewerCanEditLabel(t *testing.T) t.Setenv("TERM", "dumb") existing := basicProfile("work") existing.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } draft := seedInteractiveInitDraft("work", "work", &existing) var stderr bytes.Buffer @@ -5206,9 +5190,9 @@ func TestReviewerEntityLinearEditorManualSecretLocationStopsLabelDerivedUpdates( func TestReviewerEntityLinearEditorExistingReviewerLabelDoesNotMigrateRef(t *testing.T) { profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/existing-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/existing-reviewer"}, + DisplayName: "Old label", } cfg := config.File{ Profiles: map[string]config.Profile{"work": profile}, @@ -5241,9 +5225,9 @@ func TestReviewerEntityLinearEditorExistingReviewerLabelDoesNotMigrateRef(t *tes func TestReviewerEntityExistingReviewerChangedRefRequiresInlineSecrets(t *testing.T) { profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/existing-reviewer", - DisplayName: "Existing reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/existing-reviewer"}, + DisplayName: "Existing reviewer", } seed := seedInteractiveInitDraft("work", "work", &profile) state, err := newReviewerEntityEditorState(initReviewerEntityDraft{ @@ -5294,9 +5278,9 @@ func TestReviewerEntityExistingReviewerChangedRefRequiresInlineSecrets(t *testin func TestReviewerEntityExistingReviewerChangedRefRequiresInlineSecretsWhenStatusUnavailable(t *testing.T) { profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/existing-reviewer", - DisplayName: "Existing reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/existing-reviewer"}, + DisplayName: "Existing reviewer", } cfg := config.File{ Profiles: map[string]config.Profile{"work": profile}, @@ -5689,8 +5673,8 @@ func TestReviewerEntityLinearEditorLabelDerivedRefPreservesBackendUnavailableSta func TestHuhInitReviewerEntityStatusUpdatesWhenSecretLocationChanges(t *testing.T) { profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/old-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/old-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{"work": profile}, @@ -6239,63 +6223,6 @@ func TestInitProfileReadinessLineIncludesNotes(t *testing.T) { } } -func TestBuildInteractiveInitWorkspaceRepairsBrokenSecretsStoreSelection(t *testing.T) { - cfg := config.Normalize(config.File{ - Profiles: map[string]config.Profile{ - "work": func() config.Profile { - profile := basicProfile("work") - profile.SecretsStore = "missing-vault" - return profile - }(), - }, - }) - profile := cfg.Profiles["work"] - draft := seedInteractiveInitDraft("work", "work", &profile) - draft.SecretsStore = "" - - workspace, err := buildInteractiveInitWorkspace(&cobra.Command{}, &root.Options{}, initOptions{}, initDeps{}, filepath.Join(t.TempDir(), "config.yml"), cfg, draft) - if err != nil { - t.Fatalf("buildInteractiveInitWorkspace: %v", err) - } - if got := workspace.profile.SecretsStore; got != "" { - t.Fatalf("workspace.profile.SecretsStore = %q, want cleared explicit selection", got) - } -} - -func TestBuildInteractiveInitWorkspaceRepairsBrokenSecretsStoreToConfiguredProfile(t *testing.T) { - cfg := config.Normalize(config.File{ - Secrets: config.SecretsConfig{ - Stores: map[string]config.SecretsStore{ - "team-vault": { - DisplayName: "Team Vault", - Backend: config.SecretsStoreBackend{Kind: config.SecretsBackendKind(credstore.BackendFile)}, - }, - }, - }, - Profiles: map[string]config.Profile{ - "work": func() config.Profile { - profile := basicProfile("work") - profile.SecretsStore = "missing-vault" - return profile - }(), - }, - }) - profile := cfg.Profiles["work"] - draft := seedInteractiveInitDraft("work", "work", &profile) - draft.SecretsStore = "team-vault" - - workspace, err := buildInteractiveInitWorkspace(&cobra.Command{}, &root.Options{}, initOptions{}, initDeps{}, filepath.Join(t.TempDir(), "config.yml"), cfg, draft) - if err != nil { - t.Fatalf("buildInteractiveInitWorkspace: %v", err) - } - if got := workspace.profile.SecretsStore; got != "team-vault" { - t.Fatalf("workspace.profile.SecretsStore = %q, want team-vault", got) - } - if got := workspace.cfg.Profiles["work"].SecretsStore; got != "team-vault" { - t.Fatalf("workspace cfg secrets_profile = %q, want team-vault", got) - } -} - func TestRunInitWithDepsDeferredHintsUseSelectedSecretsStore(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yml") var stdout bytes.Buffer @@ -6353,38 +6280,6 @@ func TestRunInitWithDepsDeferredHintsUseSelectedSecretsStore(t *testing.T) { } } -func TestBuildInteractiveInitWorkspaceAllowsRepairWhileAnotherProfileStillHasBrokenSecretsStore(t *testing.T) { - cfg := config.Normalize(config.File{ - Secrets: config.SecretsConfig{ - Stores: map[string]config.SecretsStore{ - "team-vault": { - DisplayName: "Team Vault", - Backend: config.SecretsStoreBackend{Kind: config.SecretsBackendKind(credstore.BackendFile)}, - }, - }, - }, - Profiles: map[string]config.Profile{ - "home": basicProfile("home"), - "work": func() config.Profile { - profile := basicProfile("work") - profile.SecretsStore = "missing-vault" - return profile - }(), - }, - }) - home := cfg.Profiles["home"] - draft := seedInteractiveInitDraft("home", "home", &home) - draft.SecretsStore = "team-vault" - - workspace, err := buildInteractiveInitWorkspace(&cobra.Command{}, &root.Options{}, initOptions{}, initDeps{}, filepath.Join(t.TempDir(), "config.yml"), cfg, draft) - if err != nil { - t.Fatalf("buildInteractiveInitWorkspace: %v", err) - } - if got := workspace.profile.SecretsStore; got != "team-vault" { - t.Fatalf("workspace.profile.SecretsStore = %q, want team-vault", got) - } -} - func TestInitEffectiveModelMapInputValuePrefersConfiguredOverride(t *testing.T) { llm := config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -6490,7 +6385,7 @@ func TestInitInteractivePromptBuildsPlanAndPreservesOutOfScopeFields(t *testing. existing.Git.IdentityCache = "git-cache" existing.ReviewerCredentials = &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, IdentityCache: "reviewer-cache", } saveCredentialTestConfig(t, path, config.File{ @@ -6585,7 +6480,7 @@ func TestInitInteractivePromptBuildsPlanAndPreservesOutOfScopeFields(t *testing. if gotCtx.ExistingProfileName != "work" { t.Fatalf("prompt context = %#v, want existing work", gotCtx) } - if gotCtx.ExistingProfile == nil || gotCtx.ExistingProfile.Git.CredentialRef != "codereview/work" { + if gotCtx.ExistingProfile == nil || gotCtx.ExistingProfile.Git.Credential.Name != "codereview/work" { t.Fatalf("prompt existing profile = %#v, want work profile", gotCtx.ExistingProfile) } cfg, err := config.Load(path) @@ -6596,17 +6491,17 @@ func TestInitInteractivePromptBuildsPlanAndPreservesOutOfScopeFields(t *testing. t.Fatalf("old profile still present after rename: %#v", cfg.Profiles) } profile := cfg.Profiles["office"] - if profile.Git.CredentialRef != "codereview/office-git" { - t.Fatalf("git ref = %q, want office-git", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/office-git" { + t.Fatalf("git ref = %q, want office-git", profile.Git.Credential.Name) } if profile.Git.AuthMode != config.GitAuthModeGitHubApp { t.Fatalf("git auth_mode = %q, want github_app", profile.Git.AuthMode) } - if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.CredentialRef != "codereview/custom-office-reviewer" { + if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.Credential.Name != "codereview/custom-office-reviewer" { t.Fatalf("reviewer ref = %#v, want preserved custom-office-reviewer", profile.ReviewerCredentials) } - if profile.LLM.CredentialRef != "codereview/custom-office-llm" { - t.Fatalf("llm ref = %q, want custom-office-llm", profile.LLM.CredentialRef) + if profile.LLM.Credential.Name != "codereview/custom-office-llm" { + t.Fatalf("llm ref = %q, want custom-office-llm", profile.LLM.Credential.Name) } if profile.Git.IdentityCache != "" { t.Fatalf("git identity cache = %q, want cleared after git scope change", profile.Git.IdentityCache) @@ -6697,10 +6592,10 @@ func TestLoopInteractiveInitProfileV2StagesDraftIntoSessionBeforeReentry(t *test t.Fatalf("old profile still present after staged rename: %#v", next.cfg.Profiles) } profile := next.cfg.Profiles["open-cli-collective-lkjlkj"] - if profile.Git.CredentialRef != "codereview/open-cli-collective12365" { - t.Fatalf("staged git ref = %q, want edited v2 Git label", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/open-cli-collective12365" { + t.Fatalf("staged git ref = %q, want edited v2 Git label", profile.Git.Credential.Name) } - if reentryCtx.ExistingConfig.Profiles["open-cli-collective-lkjlkj"].Git.CredentialRef != "codereview/open-cli-collective12365" { + if reentryCtx.ExistingConfig.Profiles["open-cli-collective-lkjlkj"].Git.Credential.Name != "codereview/open-cli-collective12365" { t.Fatalf("reentry context profile = %#v, want staged v2 edits", reentryCtx.ExistingConfig.Profiles) } } @@ -6715,30 +6610,27 @@ func TestLoopInteractiveInitProfileV2DoesNotPromptForSelectedPrimitiveCredential "github-rianjs": { DisplayName: "github-rianjs", Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: repositoryAccessRef}, - CredentialRef: repositoryAccessRef, + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: repositoryAccessRef}, }, }, }, ReviewerEntities: map[string]config.ReviewerEntity{ "rianjs-bot": { - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: reviewerRef}, - CredentialRef: reviewerRef, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - DisplayName: "rianjs-bot", + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: reviewerRef}, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + DisplayName: "rianjs-bot", }, }, LLMRuntimes: map[string]config.LLMConfig{ "openai-api": { - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: llmRef}, - CredentialRef: llmRef, + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: llmRef}, }, }, Profiles: map[string]config.Profile{}, @@ -6801,17 +6693,17 @@ func TestLoopInteractiveInitProfileV2DoesNotPromptForSelectedPrimitiveCredential if profile.RepositoryAccess != "github-rianjs" { t.Fatalf("repository_access = %q, want github-rianjs", profile.RepositoryAccess) } - if profile.Git.CredentialRef != repositoryAccessRef { - t.Fatalf("git credential ref = %q, want selected repository access ref %q", profile.Git.CredentialRef, repositoryAccessRef) + if profile.Git.Credential.Name != repositoryAccessRef { + t.Fatalf("git credential ref = %q, want selected repository access ref %q", profile.Git.Credential.Name, repositoryAccessRef) } if profile.Reviewer.Kind != config.ProfileReviewerKindEntity || profile.Reviewer.Entity != "rianjs-bot" { t.Fatalf("reviewer = %#v, want selected rianjs-bot reviewer entity", profile.Reviewer) } - if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.CredentialRef != reviewerRef { + if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.Credential.Name != reviewerRef { t.Fatalf("reviewer credentials = %#v, want selected reviewer ref %q", profile.ReviewerCredentials, reviewerRef) } - if profile.LLMRuntime != "openai-api" || profile.LLM.CredentialRef != llmRef { - t.Fatalf("llm runtime/ref = %q/%q, want openai-api/%q", profile.LLMRuntime, profile.LLM.CredentialRef, llmRef) + if profile.LLMRuntime != "openai-api" || profile.LLM.Credential.Name != llmRef { + t.Fatalf("llm runtime/ref = %q/%q, want openai-api/%q", profile.LLMRuntime, profile.LLM.Credential.Name, llmRef) } } @@ -6885,8 +6777,8 @@ func TestLoopInteractiveInitProfileV2AppliesInlineDetailDraftParity(t *testing.T t.Fatalf("loopInteractiveInitProfileV2: %v", err) } profile := next.cfg.Profiles["work"] - if profile.Git.CredentialRef != "codereview/custom-work-git" { - t.Fatalf("git ref = %q, want custom v2 git label", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/custom-work-git" { + t.Fatalf("git ref = %q, want custom v2 git label", profile.Git.Credential.Name) } if profile.LLM.Provider != config.LLMProviderOpenAI || profile.LLM.Adapter != config.LLMAdapterCodexCLI || profile.LLM.ReviewerModelTier != config.ModelTierMedium { t.Fatalf("llm = %#v, want v2 runtime/model-tier edits", profile.LLM) @@ -6904,7 +6796,7 @@ func TestLoopInteractiveInitProfileV2AppliesInlineDetailDraftParity(t *testing.T t.Fatalf("repository_profiles = %#v, want v2 route edit", next.cfg.RepositoryProfiles) } reentryProfile := reentryCtx.ExistingConfig.Profiles["work"] - if reentryProfile.Git.CredentialRef != "codereview/custom-work-git" || !reflect.DeepEqual(reentryProfile.AgentSources, []string{"/tmp/agents"}) { + if reentryProfile.Git.Credential.Name != "codereview/custom-work-git" || !reflect.DeepEqual(reentryProfile.AgentSources, []string{"/tmp/agents"}) { t.Fatalf("reentry profile = %#v, want staged v2 detail edits", reentryProfile) } } @@ -9300,15 +9192,15 @@ func TestValidateInteractiveInitGlobalConfigWithoutProfilesStillValidatesSecrets func TestBuildInteractiveInitMenuPromptNoWorkspaceStillShowsExistingInventoryCounts(t *testing.T) { work := basicProfile("work") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } home := basicProfile("home") home.LLM = config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/home-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home-llm"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -10592,14 +10484,14 @@ func TestSynthesizeInteractiveProfileNormalizesProfileNameCredentialRefs(t *test if err != nil { t.Fatalf("synthesizeInteractiveProfile: %v", err) } - if profile.Git.CredentialRef != "codereview/open-cli-collective_x2frianjs-bot" { - t.Fatalf("Git.CredentialRef = %q, want normalized profile ref", profile.Git.CredentialRef) + if profile.Git.Credential.Name != "codereview/open-cli-collective_x2frianjs-bot" { + t.Fatalf("Git.CredentialRef = %q, want normalized profile ref", profile.Git.Credential.Name) } - if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.CredentialRef != "codereview/open-cli-collective_x2frianjs-bot-reviewer" { + if profile.ReviewerCredentials == nil || profile.ReviewerCredentials.Credential.Name != "codereview/open-cli-collective_x2frianjs-bot-reviewer" { t.Fatalf("ReviewerCredentials = %#v, want normalized reviewer ref", profile.ReviewerCredentials) } - if profile.LLM.CredentialRef != "codereview/open-cli-collective_x2frianjs-bot-llm" { - t.Fatalf("LLM.CredentialRef = %q, want normalized LLM ref", profile.LLM.CredentialRef) + if profile.LLM.Credential.Name != "codereview/open-cli-collective_x2frianjs-bot-llm" { + t.Fatalf("LLM.CredentialRef = %q, want normalized LLM ref", profile.LLM.Credential.Name) } } @@ -12844,8 +12736,8 @@ func TestInitInteractiveMenuFocusedLLMRuntimeRebuildsSecretPlanning(t *testing.T if profile.LLM.Auth != config.LLMAuthAPIKey || profile.LLM.Adapter != config.LLMAdapterOpenAIAPI { t.Fatalf("llm profile = %#v, want openai api-key runtime", profile.LLM) } - if profile.LLM.CredentialRef == "" { - t.Fatalf("llm credential ref = %q, want generated ref after runtime rebuild", profile.LLM.CredentialRef) + if profile.LLM.Credential.Name == "" { + t.Fatalf("llm credential ref = %q, want generated ref after runtime rebuild", profile.LLM.Credential.Name) } if cfg.Data.Retention.MaxAgeDaysValue() != 30 || cfg.Data.Retention.Enforcement != config.RetentionManualOnly { t.Fatalf("global settings after runtime rebuild = %#v, want 30/manual_only", cfg.Data.Retention) @@ -12948,8 +12840,8 @@ func TestInitInteractiveMenuFocusedLLMRuntimePreservesUnrelatedProfileState(t *t if got.LLM.Provider != config.LLMProviderOpenAI || got.LLM.Adapter != config.LLMAdapterCodexCLI || got.LLM.Auth != config.LLMAuthSubscription { t.Fatalf("llm = %#v, want codex subscription runtime", got.LLM) } - if got.LLM.CredentialRef != "" { - t.Fatalf("llm credential ref = %q, want cleared for subscription runtime", got.LLM.CredentialRef) + if got.LLM.Credential.Name != "" { + t.Fatalf("llm credential ref = %q, want cleared for subscription runtime", got.LLM.Credential.Name) } if got.Git != profile.Git { t.Fatalf("git = %#v, want preserved %#v", got.Git, profile.Git) @@ -13248,8 +13140,8 @@ func TestInitInteractiveMenuFocusedReviewerEntityRebuildsSecretPlanning(t *testi if gotEntity == nil { t.Fatalf("reviewer_entities = %#v, want staged GitHub App reviewer entity", cfg.ReviewerEntities) } - if gotEntity.CredentialRef == "" { - t.Fatalf("reviewer entity credential ref = %q, want generated ref after reviewer rebuild", gotEntity.CredentialRef) + if gotEntity.Credential.Name == "" { + t.Fatalf("reviewer entity credential ref = %q, want generated ref after reviewer rebuild", gotEntity.Credential.Name) } if cfg.Data.Retention.MaxAgeDaysValue() != 14 || cfg.Data.Retention.Enforcement != config.RetentionAtWrite { t.Fatalf("global settings after reviewer rebuild = %#v, want 14/at_write", cfg.Data.Retention) @@ -13374,7 +13266,7 @@ func TestInitCredentialDestinationDescriptionNamedOnePasswordShowsRoutingWithout }, }, } - resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{SecretsStore: "team-vault"}) + resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{Git: config.GitConfig{Credential: config.CredentialLocation{Store: "team-vault"}}}) if err != nil { t.Fatalf("ResolveSecretsStoreForProfile: %v", err) } @@ -13434,7 +13326,7 @@ func TestInitCredentialDestinationDescriptionOnePasswordConnectDoesNotReadTokenV }, }, } - resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{SecretsStore: "connect-vault"}) + resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{Git: config.GitConfig{Credential: config.CredentialLocation{Store: "connect-vault"}}}) if err != nil { t.Fatalf("ResolveSecretsStoreForProfile: %v", err) } @@ -13479,7 +13371,7 @@ func TestInitCredentialDestinationDescriptionOnePasswordDesktopShowsAccountID(t }, }, } - resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{SecretsStore: "desktop-vault"}) + resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, config.Profile{Git: config.GitConfig{Credential: config.CredentialLocation{Store: "desktop-vault"}}}) if err != nil { t.Fatalf("ResolveSecretsStoreForProfile: %v", err) } @@ -13890,8 +13782,8 @@ func TestInitReviewerCredentialStatusIncludesSelectableReviewerEntities(t *testi work := basicProfile("work") bot := basicProfile("bot") bot.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -14104,9 +13996,9 @@ func TestMergeReviewerCredentialDraftWritesDropsStaleKeysWhenAuthModeChanges(t * profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: ref, + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, } if _, err := planInitCredentialsWithConfig(config.File{Profiles: map[string]config.Profile{"work": profile}}, nil, profile, projectInitPlannedWriteKeys(session.writes)); err != nil { t.Fatalf("planInitCredentialsWithConfig: %v", err) @@ -14135,8 +14027,8 @@ func TestMergeReviewerCredentialDraftWritesDropsStaleKeysWhenAuthModeChanges(t * profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: ref, + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, } if _, err := planInitCredentialsWithConfig(config.File{Profiles: map[string]config.Profile{"work": profile}}, nil, profile, projectInitPlannedWriteKeys(session.writes)); err != nil { t.Fatalf("planInitCredentialsWithConfig: %v", err) @@ -14173,9 +14065,9 @@ func TestInitInteractiveMenuFocusedGitHubAppReviewerInlineWritesReadyWithoutHint Profiles: map[string]config.Profile{ "work": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -14244,7 +14136,7 @@ func TestInitInteractiveMenuFocusedGitHubAppReviewerInlineWritesReadyWithoutHint } entity := got.ReviewerEntities["reviewer-github-app"] if entity.AuthMode != config.GitAuthModeGitHubApp || - entity.CredentialRef != "codereview/rianjs-bot" || + entity.Credential.Name != "codereview/rianjs-bot" || entity.DisplayName != "rianjs-bot" { t.Fatalf("reviewer entity = %#v, want rianjs-bot GitHub App reviewer", entity) } @@ -14737,7 +14629,7 @@ func TestInitInteractiveMenuReviewerCredentialDecisionDropsAfterReviewerRefChang t.Fatalf("Load config: %v", err) } entity := got.ReviewerEntities["reviewer-github-app"] - if entity.CredentialRef != "codereview/new-reviewer" { + if entity.Credential.Name != "codereview/new-reviewer" { t.Fatalf("reviewer entity = %#v, want new reviewer ref", entity) } if entity.GitHubApp == nil || entity.GitHubApp.AppID != "222" { @@ -14968,14 +14860,14 @@ func TestInitInteractiveMenuFocusedReviewerEntitySavePreservesCustomCredentialRe } cfg.Profiles["work"] = config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/custom-work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work-reviewer"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -15047,8 +14939,8 @@ func TestInitInteractiveMenuFocusedReviewerEntitySavePreservesCustomCredentialRe if profile.ReviewerCredentials == nil { t.Fatal("reviewer credentials = nil, want custom reviewer ref preserved after save") } - if profile.ReviewerCredentials.CredentialRef != "codereview/custom-work-reviewer" { - t.Fatalf("reviewer credential ref = %q, want custom reviewer ref preserved after save", profile.ReviewerCredentials.CredentialRef) + if profile.ReviewerCredentials.Credential.Name != "codereview/custom-work-reviewer" { + t.Fatalf("reviewer credential ref = %q, want custom reviewer ref preserved after save", profile.ReviewerCredentials.Credential.Name) } } @@ -15058,15 +14950,15 @@ func TestInitInteractiveMenuFocusedReviewerEntityLabelOnlySaveSkipsCredentialWri Profiles: map[string]config.Profile{ "work": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "Old label", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "Old label", }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -15138,7 +15030,7 @@ func TestInitInteractiveMenuFocusedReviewerEntityLabelOnlySaveSkipsCredentialWri if got, want := profile.ReviewerCredentials.DisplayName, "OC Collective bot"; got != want { t.Fatalf("reviewer display name = %q, want %q", got, want) } - if got, want := profile.ReviewerCredentials.CredentialRef, "codereview/work-reviewer"; got != want { + if got, want := profile.ReviewerCredentials.Credential.Name, "codereview/work-reviewer"; got != want { t.Fatalf("reviewer credential ref = %q, want %q", got, want) } } @@ -15149,14 +15041,14 @@ func TestInitInteractiveMenuFocusedReviewerEntitySavePreservesExistingCredential Profiles: map[string]config.Profile{ "work": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/custom-work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work-reviewer"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -15243,8 +15135,8 @@ func TestInitInteractiveMenuFocusedReviewerEntitySavePreservesExistingCredential if profile.ReviewerCredentials == nil { t.Fatal("reviewer credentials = nil, want existing reviewer ref preserved after save") } - if profile.ReviewerCredentials.CredentialRef != "codereview/custom-work-reviewer" { - t.Fatalf("reviewer credential ref = %q, want existing reviewer ref preserved after clearing draft ref", profile.ReviewerCredentials.CredentialRef) + if profile.ReviewerCredentials.Credential.Name != "codereview/custom-work-reviewer" { + t.Fatalf("reviewer credential ref = %q, want existing reviewer ref preserved after clearing draft ref", profile.ReviewerCredentials.Credential.Name) } } @@ -15364,9 +15256,9 @@ func TestInitInteractiveMenuFocusedReviewerEntityDeleteUndoReturnsToMenu(t *test "work": func() config.Profile { profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } return profile }(), @@ -15900,15 +15792,15 @@ func TestInitInteractiveMenuDeleteUndoAndSaveFlow(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yml") work := basicProfile("work") work.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } home := basicProfile("home") home.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } saveCredentialTestConfig(t, path, config.File{ Profiles: map[string]config.Profile{ @@ -16243,8 +16135,8 @@ func TestInitInteractiveMenuFinalSaveSetNowWritesCredentialsAndMarksProfileReady if err != nil { t.Fatalf("Load config: %v", err) } - if cfg.Profiles["default"].Git.CredentialRef != "codereview/default" { - t.Fatalf("suggested profile git ref = %q, want codereview/default", cfg.Profiles["default"].Git.CredentialRef) + if cfg.Profiles["default"].Git.Credential.Name != "codereview/default" { + t.Fatalf("suggested profile git ref = %q, want codereview/default", cfg.Profiles["default"].Git.Credential.Name) } if !strings.Contains(stdout.String(), "Saved staged init changes") || !strings.Contains(stdout.String(), "- review profiles: 1") || !strings.Contains(stdout.String(), "- credential secrets: 1 name") || !strings.Contains(stdout.String(), "- default: ready") { t.Fatalf("stdout = %q, want ready summary for suggested profile", stdout.String()) @@ -16456,7 +16348,7 @@ func TestInitInteractiveMenuGlobalSettingsOnlySaveDoesNotFinalizeBootstrappedPro t.Fatalf("profiles = %#v, want only existing work profile", got) } work := cfg.Profiles["work"] - if work.Git.Host != "github.com" || work.Git.AuthMode != config.GitAuthModePAT || work.Git.CredentialRef != "codereview/work" { + if work.Git.Host != "github.com" || work.Git.AuthMode != config.GitAuthModePAT || work.Git.Credential.Name != "codereview/work" { t.Fatalf("work git = %#v, want untouched bootstrap profile git config", work.Git) } if work.ReviewerCredentials != nil { @@ -16522,7 +16414,7 @@ func TestInitInteractiveMenuSecretsManagementOnlySaveDoesNotFinalizeBootstrapped t.Fatalf("Load config: %v", err) } work := cfg.Profiles["work"] - if work.Git.Host != "github.com" || work.Git.AuthMode != config.GitAuthModePAT || work.Git.CredentialRef != "codereview/work" { + if work.Git.Host != "github.com" || work.Git.AuthMode != config.GitAuthModePAT || work.Git.Credential.Name != "codereview/work" { t.Fatalf("work git = %#v, want untouched bootstrap profile git config", work.Git) } if work.ReviewerCredentials != nil { @@ -17060,12 +16952,12 @@ func TestApplyInteractiveInitSessionPlanWritesSeparateSecretsStoresIndependently Profiles: map[string]config.Profile{ "home": func() config.Profile { p := basicProfile("home") - p.SecretsStore = "personal-memory" + p.Git.Credential.Store = "personal-memory" return p }(), "work": func() config.Profile { p := basicProfile("work") - p.SecretsStore = "work-file" + p.Git.Credential.Store = "work-file" return p }(), }, @@ -17128,11 +17020,11 @@ func TestApplyInteractiveInitSessionPlanWritesSeparateSecretsStoresIndependently func TestApplyInteractiveInitSessionPlanWritesReviewerSecretsToResolvedStore(t *testing.T) { workStore := newFakeInitStore(nil) profile := basicProfile("work") - profile.SecretsStore = "work-file" + profile.Git.Credential.Store = "work-file" profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: "work-file", Name: "codereview/work-reviewer"}, } cfg := config.File{ Secrets: config.SecretsConfig{ @@ -17221,7 +17113,7 @@ func TestApplyInteractiveInitSessionPlanNamedSecretsStoreWriteFailureStopsConfig Profiles: map[string]config.Profile{ "work": func() config.Profile { p := basicProfile("work") - p.SecretsStore = "work-1password" + p.Git.Credential.Store = "work-1password" return p }(), }, @@ -17662,8 +17554,8 @@ func TestInitInteractiveCollectsClipboardGitSecretWithoutHint(t *testing.T) { if strings.Contains(stderr.String(), "set-credential --store local-os --name codereview/default --key "+credentials.GitTokenKey) { t.Fatalf("stderr = %q, want no stale follow-up hint after collected secret", stderr.String()) } - if savedCfg.Profiles["default"].Git.CredentialRef != "codereview/default" { - t.Fatalf("git ref = %q, want codereview/default", savedCfg.Profiles["default"].Git.CredentialRef) + if savedCfg.Profiles["default"].Git.Credential.Name != "codereview/default" { + t.Fatalf("git ref = %q, want codereview/default", savedCfg.Profiles["default"].Git.Credential.Name) } } @@ -18150,10 +18042,10 @@ func TestApplyInteractiveInitSessionPlanDeletesStaleReviewerKeyWithoutWrites(t * }) profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "work bot", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "work bot", } cfg := config.File{ Profiles: map[string]config.Profile{"work": profile}, @@ -18227,9 +18119,9 @@ func TestApplyInteractiveInitSessionPlanSaveFailureDoesNotDeleteStaleReviewerKey }) profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } cfg := config.File{Profiles: map[string]config.Profile{"work": profile}} resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, profile) @@ -18277,14 +18169,14 @@ func TestApplyInteractiveInitSessionPlanKeepsSharedRefPATKeyAfterStagedAuthSwitc }) appProfile := basicProfile("app") appProfile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } patProfile := basicProfile("pat") patProfile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -18349,10 +18241,10 @@ func TestApplyInitPlanDeletesStaleReviewerKeyWithoutWrites(t *testing.T) { }) profile := basicProfile("work") profile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/work-reviewer", - DisplayName: "work bot", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, + DisplayName: "work bot", } cfg := config.File{Profiles: map[string]config.Profile{"work": profile}} resolved, err := credentials.ResolveSecretsStoreForProfile(cfg, profile) @@ -18426,14 +18318,14 @@ func TestStaleReviewerCleanupKeepsKeysRequiredByAnotherActiveModeWithoutWrites(t } appProfile := basicProfile("app") appProfile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: "12345"}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } patProfile := basicProfile("pat") patProfile.ReviewerCredentials = &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/shared-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-reviewer"}, } cfg := config.File{ Profiles: map[string]config.Profile{ @@ -18784,10 +18676,9 @@ func basicProfile(profile string) config.Profile { } return config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, - CredentialRef: ref, + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -18810,11 +18701,10 @@ func apiKeyProfile(profile string, provider config.LLMProvider) config.Profile { p := basicProfile(profile) ref := "codereview/" + profile + "-llm" p.LLM = config.LLMConfig{ - Provider: provider, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, - CredentialRef: ref, + Provider: provider, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, } if provider == config.LLMProviderOpenAI { p.LLM.Adapter = config.LLMAdapterOpenAIAPI diff --git a/internal/cmd/mecmd/mecmd.go b/internal/cmd/mecmd/mecmd.go index d1e54fc..adb078b 100644 --- a/internal/cmd/mecmd/mecmd.go +++ b/internal/cmd/mecmd/mecmd.go @@ -217,7 +217,7 @@ func (r *githubResolver) ResolveIdentity(ctx context.Context, profileName string if newClient == nil { newClient = githubprovider.NewFromGitConfig } - resolvedSecretsStore, err := credentials.ResolveSecretsStoreForRef(r.cfg, git.CredentialRef, profileName) + resolvedSecretsStore, err := credentials.ResolveSecretsStoreForRef(r.cfg, git.Credential.Name, profileName) if err != nil { return gitprovider.Identity{}, err } diff --git a/internal/cmd/mecmd/mecmd_test.go b/internal/cmd/mecmd/mecmd_test.go index b9907ac..c1875bf 100644 --- a/internal/cmd/mecmd/mecmd_test.go +++ b/internal/cmd/mecmd/mecmd_test.go @@ -157,7 +157,7 @@ func TestMeProfileUsesReviewerCredentials(t *testing.T) { if got := work.ReviewerCredentials.IdentityCache; got != "bot" { t.Fatalf("reviewer identity cache = %q, want bot", got) } - if len(resolver.calls) != 1 || resolver.calls[0].CredentialRef != "codereview/work-reviewer" { + if len(resolver.calls) != 1 || resolver.calls[0].Credential.Name != "codereview/work-reviewer" { t.Fatalf("resolver calls = %#v, want reviewer ref", resolver.calls) } } @@ -186,7 +186,7 @@ func TestMeReviewerCredentialsTakePrecedenceOverGitHubAppRepositoryAccess(t *tes work := cfg.Profiles["work"] work.Git.AuthMode = config.GitAuthModeGitHubApp work.Git.GitHubApp = &config.GitHubAppConfig{AppID: "12345"} - work.Git.CredentialRef = "codereview/work-app" + work.Git.Credential.Name = "codereview/work-app" work.Git.Credential.Name = "codereview/work-app" cfg.Profiles["work"] = work path := saveTestConfig(t, cfg) @@ -627,7 +627,7 @@ profiles: } if len(resolver.calls) != 1 || resolver.calls[0].AuthMode != config.GitAuthModePAT || - resolver.calls[0].CredentialRef != "codereview/work-reviewer" { + resolver.calls[0].Credential.Name != "codereview/work-reviewer" { t.Fatalf("resolver calls = %#v, want reviewer PAT config", resolver.calls) } } @@ -812,17 +812,17 @@ func TestGitHubResolverUsesCR08FactoryAndCredentialStore(t *testing.T) { }, } gitIdentity, err := resolver.ResolveIdentity(context.Background(), "work", config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }) if err != nil { t.Fatalf("ResolveIdentity git: %v", err) } reviewerIdentity, err := resolver.ResolveIdentity(context.Background(), "work", config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, }) if err != nil { t.Fatalf("ResolveIdentity reviewer: %v", err) @@ -841,11 +841,10 @@ func TestOrgDeploymentPrestagedMultiRefCredentialsHealthChecks(t *testing.T) { cfg := fileBackendConfig(t) work := cfg.Profiles["work"] work.LLM = config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: testFileCredentialStoreID, Name: "codereview/work-llm"}, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: testFileCredentialStoreID, Name: "codereview/work-llm"}, } work.AgentSources = []string{"~/.config/codereview/agents"} work.ReviewPolicy = config.ReviewPolicy{MajorEvent: config.ReviewMajorEventComment} @@ -962,10 +961,10 @@ type fakeResolver struct { func (f *fakeResolver) ResolveIdentity(_ context.Context, _ string, git config.GitConfig) (gitprovider.Identity, error) { f.calls = append(f.calls, git) - if err := f.errs[git.CredentialRef]; err != nil { + if err := f.errs[git.Credential.Name]; err != nil { return gitprovider.Identity{}, err } - return f.identities[git.CredentialRef], nil + return f.identities[git.Credential.Name], nil } func newTestCommand(path string, resolver identity.Resolver) (*cobra.Command, *bytes.Buffer) { @@ -1111,7 +1110,6 @@ func testConfig() config.File { Host: "github.com", AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, - CredentialRef: "codereview/home", IdentityCache: "old-home", }, LLM: config.LLMConfig{ @@ -1126,13 +1124,11 @@ func testConfig() config.File { Host: "github.com", AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work"}, - CredentialRef: "codereview/work", IdentityCache: "work-user-cache", }, ReviewerCredentials: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", IdentityCache: "old-bot", }, LLM: config.LLMConfig{ diff --git a/internal/cmd/noleak/noleak_test.go b/internal/cmd/noleak/noleak_test.go index ce0a8c1..29fb8f8 100644 --- a/internal/cmd/noleak/noleak_test.go +++ b/internal/cmd/noleak/noleak_test.go @@ -541,23 +541,20 @@ func (h *auditHarness) config() config.File { Profiles: map[string]config.Profile{ "default": { Git: config.GitConfig{ - Host: h.prRef.Host, - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default"}, - CredentialRef: "codereview/default", + Host: h.prRef.Host, + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default"}, }, ReviewerCredentials: &config.ReviewerCredentials{ - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default-reviewer"}, - CredentialRef: "codereview/default-reviewer", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default-reviewer"}, }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default-llm"}, - CredentialRef: "codereview/default-llm", - ModelMap: config.ModelMap{"medium": "claude-sonnet-4-6"}, + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default-llm"}, + ModelMap: config.ModelMap{"medium": "claude-sonnet-4-6"}, }, AgentSources: []string{h.agentDir}, ReviewPolicy: config.ReviewPolicy{ @@ -579,7 +576,7 @@ func (h *auditHarness) githubAppGitConfig() config.File { profile.Git.AuthMode = config.GitAuthModeGitHubApp profile.Git.GitHubApp = &config.GitHubAppConfig{AppID: h.githubAppIDSecret} profile.Git.Credential = config.CredentialLocation{Store: auditCredentialStoreID, Name: "codereview/default-app"} - profile.Git.CredentialRef = "codereview/default-app" + profile.Git.Credential.Name = "codereview/default-app" cfg.Profiles["default"] = profile return cfg } @@ -866,7 +863,6 @@ func gitConfigForReviewerAuth(profile config.Profile) config.GitConfig { AuthMode: profile.ReviewerCredentials.AuthMode, GitHubApp: profile.ReviewerCredentials.GitHubApp, Credential: profile.ReviewerCredentials.Credential, - CredentialRef: profile.ReviewerCredentials.CredentialRef, IdentityCache: profile.ReviewerCredentials.IdentityCache, } } diff --git a/internal/cmd/respondcmd/respondcmd_test.go b/internal/cmd/respondcmd/respondcmd_test.go index ec604d3..1772beb 100644 --- a/internal/cmd/respondcmd/respondcmd_test.go +++ b/internal/cmd/respondcmd/respondcmd_test.go @@ -122,7 +122,7 @@ func TestRespondPassesRetentionConfigToRuntimeFactory(t *testing.T) { func TestRespondRejectsAmbiguousRepositoryProfileRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{ { diff --git a/internal/cmd/reviewcmd/reviewcmd_test.go b/internal/cmd/reviewcmd/reviewcmd_test.go index 5bb6289..610dff9 100644 --- a/internal/cmd/reviewcmd/reviewcmd_test.go +++ b/internal/cmd/reviewcmd/reviewcmd_test.go @@ -113,8 +113,8 @@ func TestReviewCommandAcceptanceHarnessComposesDryRun(t *testing.T) { var cleanupCalled bool cmd, out, errOut := newTestCommandWithStderr(t, testConfig(), func(_ context.Context, opts app.OpenRequest) (app.Runtime, error) { gotRuntime = opts - if opts.Profile.Git.CredentialRef != "codereview/home" { - t.Fatalf("runtime profile credential ref = %q, want repository-routed home profile", opts.Profile.Git.CredentialRef) + if opts.Profile.Git.Credential.Name != "codereview/home" { + t.Fatalf("runtime profile credential ref = %q, want repository-routed home profile", opts.Profile.Git.Credential.Name) } return app.Runtime{ Runner: runner, @@ -196,7 +196,7 @@ func TestReviewCommandAcceptanceHarnessComposesDryRun(t *testing.T) { func TestReviewUsesRepositoryProfileRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -208,8 +208,8 @@ func TestReviewUsesRepositoryProfileRoute(t *testing.T) { }} runner := &fakeRunner{result: testPipelineResult(false)} cmd, _ := newTestCommand(t, cfg, func(_ context.Context, req app.OpenRequest) (app.Runtime, error) { - if req.Profile.Git.CredentialRef != "codereview/work" { - t.Fatalf("runtime profile credential ref = %q, want work route", req.Profile.Git.CredentialRef) + if req.Profile.Git.Credential.Name != "codereview/work" { + t.Fatalf("runtime profile credential ref = %q, want work route", req.Profile.Git.Credential.Name) } return app.Runtime{Runner: runner, PostingIdentity: gitprovider.Identity{Login: "review-bot", ID: "bot-id"}}, nil }) @@ -225,7 +225,7 @@ func TestReviewUsesRepositoryProfileRoute(t *testing.T) { func TestReviewRejectsAmbiguousRepositoryProfileRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{ { @@ -262,7 +262,7 @@ func TestReviewRejectsAmbiguousRepositoryProfileRoute(t *testing.T) { func TestReviewExplicitProfileBypassesRepositoryRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -274,8 +274,8 @@ func TestReviewExplicitProfileBypassesRepositoryRoute(t *testing.T) { }} runner := &fakeRunner{result: testPipelineResult(false)} cmd, _ := newTestCommand(t, cfg, func(_ context.Context, req app.OpenRequest) (app.Runtime, error) { - if req.Profile.Git.CredentialRef != "codereview/home" { - t.Fatalf("runtime profile credential ref = %q, want explicit home", req.Profile.Git.CredentialRef) + if req.Profile.Git.Credential.Name != "codereview/home" { + t.Fatalf("runtime profile credential ref = %q, want explicit home", req.Profile.Git.Credential.Name) } return app.Runtime{Runner: runner, PostingIdentity: gitprovider.Identity{Login: "review-bot", ID: "bot-id"}}, nil }) @@ -291,7 +291,7 @@ func TestReviewExplicitProfileBypassesRepositoryRoute(t *testing.T) { func TestReviewExplicitEmptyProfileFailsBeforeRepositoryRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -316,7 +316,7 @@ func TestReviewExplicitEmptyProfileFailsBeforeRepositoryRoute(t *testing.T) { func TestReviewUnmatchedRepositoryRequiresProfileOrRoute(t *testing.T) { cfg := testConfig() work := cfg.Profiles["home"] - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cfg.RepositoryProfiles = []config.RepositoryProfile{{ Profile: "work", @@ -346,7 +346,7 @@ func TestReviewExplicitProfileHostMismatch(t *testing.T) { cfg.RepositoryProfiles = nil work := home work.Git.Host = "github.com" - work.Git.CredentialRef = "codereview/work" + work.Git.Credential.Name = "codereview/work" cfg.Profiles["work"] = work cmd, _ := newTestCommand(t, cfg, func(context.Context, app.OpenRequest) (app.Runtime, error) { t.Fatal("runtime factory should not be called when route profile host mismatches") diff --git a/internal/config/config.go b/internal/config/config.go index 7615943..a213820 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -150,8 +150,6 @@ type Profile struct { ReviewPolicy ReviewPolicy `yaml:"review_policy,omitempty" json:"review_policy"` Hooks []Hook `yaml:"hooks,omitempty" json:"hooks,omitempty"` - // SecretsStore is retained as an ignored in-memory compatibility field. - SecretsStore string `yaml:"-" json:"-"` // ReviewerCredentials is the resolved effective reviewer credential block // derived from ReviewerEntities for runtime callers. It is not config schema. ReviewerCredentials *ReviewerCredentials `yaml:"-" json:"-"` @@ -239,9 +237,6 @@ type GitConfig struct { Credential CredentialLocation `yaml:"credential" json:"credential"` GitHubApp *GitHubAppConfig `yaml:"github_app,omitempty" json:"github_app,omitempty"` IdentityCache string `yaml:"identity_cache,omitempty" json:"identity_cache,omitempty"` - - // CredentialRef is retained as an ignored in-memory compatibility field. - CredentialRef string `yaml:"-" json:"-"` } // GitHubAppConfig stores non-secret GitHub App identifiers. @@ -262,9 +257,6 @@ type ReviewerCredentials struct { GitHubApp *GitHubAppConfig `yaml:"github_app,omitempty" json:"github_app,omitempty"` DisplayName string `yaml:"display_name,omitempty" json:"display_name,omitempty"` IdentityCache string `yaml:"identity_cache,omitempty" json:"identity_cache,omitempty"` - - // CredentialRef is retained as an ignored in-memory compatibility field. - CredentialRef string `yaml:"-" json:"-"` } // ProfileReviewer selects the identity used to post review comments. @@ -328,9 +320,6 @@ type ReviewerEntity struct { GitHubApp *GitHubAppConfig `yaml:"github_app,omitempty" json:"github_app,omitempty"` DisplayName string `yaml:"display_name,omitempty" json:"display_name,omitempty"` IdentityCache string `yaml:"identity_cache,omitempty" json:"identity_cache,omitempty"` - - // CredentialRef is retained as an ignored in-memory compatibility field. - CredentialRef string `yaml:"-" json:"-"` } // RepositoryProfile routes repositories to profiles when --profile is omitted. @@ -354,9 +343,6 @@ type LLMConfig struct { Credential CredentialLocation `yaml:"credential,omitempty" json:"credential,omitempty"` ModelMap ModelMap `yaml:"model_map,omitempty" json:"model_map,omitempty"` ReviewerModelTier ModelTier `yaml:"reviewer_model_tier,omitempty" json:"reviewer_model_tier,omitempty"` - - // CredentialRef is retained as an ignored in-memory compatibility field. - CredentialRef string `yaml:"-" json:"-"` } // ModelMap maps portable model tiers to provider-specific model identifiers. @@ -1936,7 +1922,6 @@ func reviewerEntityFromProfile(profile Profile) ReviewerEntity { Host: profile.Git.Host, AuthMode: reviewer.AuthMode, Credential: reviewer.Credential, - CredentialRef: reviewer.CredentialRef, GitHubApp: cloneGitHubAppConfig(reviewer.GitHubApp), DisplayName: reviewer.DisplayName, IdentityCache: reviewer.IdentityCache, @@ -2102,7 +2087,6 @@ func IsOnePasswordSecretsBackend(kind SecretsBackendKind) bool { } func (p Profile) normalized() Profile { - p.SecretsStore = strings.TrimSpace(p.SecretsStore) p.RepositoryAccess = strings.TrimSpace(p.RepositoryAccess) p.Git = p.Git.normalized() p.Reviewer = p.Reviewer.normalized() @@ -2144,7 +2128,7 @@ func (i ProfileReviewerGitHubAppInstallation) normalized() ProfileReviewerGitHub func (g GitConfig) normalized() GitConfig { g.Host = normalizeConfigHost(g.Host) g.AuthMode = GitAuthMode(strings.TrimSpace(string(g.AuthMode))) - g.CredentialRef, g.Credential = syncCredentialRef(g.CredentialRef, g.Credential) + g.Credential = g.Credential.normalized() g.GitHubApp = cloneGitHubAppConfig(g.GitHubApp) return g } @@ -2165,7 +2149,7 @@ func (r RepositoryAccessConfig) normalized() RepositoryAccessConfig { } func (r ReviewerCredentials) normalized() ReviewerCredentials { - r.CredentialRef, r.Credential = syncCredentialRef(r.CredentialRef, r.Credential) + r.Credential = r.Credential.normalized() r.GitHubApp = cloneGitHubAppConfig(r.GitHubApp) return r } @@ -2173,7 +2157,7 @@ func (r ReviewerCredentials) normalized() ReviewerCredentials { func (r ReviewerEntity) normalized() ReviewerEntity { r.Host = normalizeConfigHost(r.Host) r.AuthMode = GitAuthMode(strings.TrimSpace(string(r.AuthMode))) - r.CredentialRef, r.Credential = syncCredentialRef(r.CredentialRef, r.Credential) + r.Credential = r.Credential.normalized() r.GitHubApp = cloneGitHubAppConfig(r.GitHubApp) r.DisplayName = strings.TrimSpace(r.DisplayName) r.IdentityCache = strings.TrimSpace(r.IdentityCache) @@ -2188,7 +2172,6 @@ func (r ReviewerEntity) reviewerCredentials() *ReviewerCredentials { GitHubApp: cloneGitHubAppConfig(r.GitHubApp), DisplayName: r.DisplayName, IdentityCache: r.IdentityCache, - CredentialRef: r.Credential.Name, } } @@ -2202,7 +2185,7 @@ func cloneGitHubAppConfig(app *GitHubAppConfig) *GitHubAppConfig { } func (l LLMConfig) normalized() LLMConfig { - l.CredentialRef, l.Credential = syncCredentialRef(l.CredentialRef, l.Credential) + l.Credential = l.Credential.normalized() l.ReviewerModelTier = ModelTier(strings.TrimSpace(string(l.ReviewerModelTier))) if len(l.ModelMap) > 0 { modelMap := make(ModelMap, len(l.ModelMap)) @@ -2214,26 +2197,13 @@ func (l LLMConfig) normalized() LLMConfig { return l } -func syncCredentialRef(ref string, credential CredentialLocation) (string, CredentialLocation) { - ref = strings.TrimSpace(ref) - credential = credential.normalized() - if credential.Name == "" && ref != "" { - if credential.Store == "" { - credential.Store = LocalOSCredentialStoreID - } - credential.Name = ref - } - return credential.Name, credential -} - func (l LLMConfig) empty() bool { return strings.TrimSpace(string(l.Provider)) == "" && strings.TrimSpace(string(l.Auth)) == "" && strings.TrimSpace(string(l.Adapter)) == "" && l.Credential.empty() && len(l.ModelMap) == 0 && - strings.TrimSpace(string(l.ReviewerModelTier)) == "" && - strings.TrimSpace(l.CredentialRef) == "" + strings.TrimSpace(string(l.ReviewerModelTier)) == "" } func (p ReviewPolicy) normalized() ReviewPolicy { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 36f589e..15dcf1a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -251,7 +251,7 @@ func TestValidateRejectsInvalidPiCombinations(t *testing.T) { }{ {name: "api key auth", mutate: func(llm *LLMConfig) { llm.Auth = LLMAuthAPIKey - llm.CredentialRef = "codereview/pi-llm" + llm.Credential = CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/pi-llm"} }}, {name: "claude cli adapter", mutate: func(llm *LLMConfig) { llm.Adapter = LLMAdapterClaudeCLI @@ -271,7 +271,6 @@ func TestValidateRejectsInvalidPiCombinations(t *testing.T) { llm.Provider = LLMProviderPi llm.Auth = LLMAuthSubscription llm.Adapter = LLMAdapterPiRPC - llm.CredentialRef = "" llm.Credential = CredentialLocation{} tt.mutate(&llm) cfg.LLMRuntimes["home-llm"] = llm @@ -296,7 +295,7 @@ func TestValidateRejectsInvalidCodexCLICombinations(t *testing.T) { }}, {name: "api key auth", mutate: func(llm *LLMConfig) { llm.Auth = LLMAuthAPIKey - llm.CredentialRef = "codereview/codex-llm" + llm.Credential = CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/codex-llm"} }}, } @@ -307,7 +306,6 @@ func TestValidateRejectsInvalidCodexCLICombinations(t *testing.T) { llm.Provider = LLMProviderOpenAI llm.Auth = LLMAuthSubscription llm.Adapter = LLMAdapterCodexCLI - llm.CredentialRef = "" llm.Credential = CredentialLocation{} tt.mutate(&llm) cfg.LLMRuntimes["home-llm"] = llm @@ -326,7 +324,7 @@ func TestValidateRejectsClaudeCLIAPIKeyAuth(t *testing.T) { cfg := validFile() llm := cfg.LLMRuntimes["home-llm"] llm.Auth = LLMAuthAPIKey - llm.CredentialRef = "codereview/claude-llm" + llm.Credential = CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/claude-llm"} cfg.LLMRuntimes["home-llm"] = llm err := Validate(cfg) @@ -590,8 +588,8 @@ func TestRepositoryProfileRoutesRoundTripAndResolve(t *testing.T) { if err != nil { t.Fatalf("ResolveProfileForRepository: %v", err) } - if name != tt.wantProfile || profile.Git.CredentialRef != tt.wantCredRef { - t.Fatalf("resolved (%q,%q), want (%q,%q)", name, profile.Git.CredentialRef, tt.wantProfile, tt.wantCredRef) + if name != tt.wantProfile || profile.Git.Credential.Name != tt.wantCredRef { + t.Fatalf("resolved (%q,%q), want (%q,%q)", name, profile.Git.Credential.Name, tt.wantProfile, tt.wantCredRef) } }) } @@ -617,8 +615,8 @@ func TestRepositoryProfileRoutesRoundTripAndResolve(t *testing.T) { if err != nil { t.Fatalf("ResolveProfileForRepository inverse order: %v", err) } - if name != "work" || profile.Git.CredentialRef != "codereview/work" { - t.Fatalf("inverse-order resolved (%q,%q), want work route", name, profile.Git.CredentialRef) + if name != "work" || profile.Git.Credential.Name != "codereview/work" { + t.Fatalf("inverse-order resolved (%q,%q), want work route", name, profile.Git.Credential.Name) } _, _, err = ResolveProfileForRepository(loaded, "", false, RepositoryTarget{Host: "github.com", Namespace: "open-cli-collective", Repo: "codereview-cli"}) @@ -976,9 +974,8 @@ func TestCredentialRefsIncludesGitHubAppModes(t *testing.T) { func TestPinnedGitHubAppInstallationIDForGit(t *testing.T) { profile := validFile().normalized().Profiles["work"] profile.ReviewerCredentials = &ReviewerCredentials{ - AuthMode: GitAuthModeGitHubApp, - Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, - CredentialRef: "codereview/work-reviewer", + AuthMode: GitAuthModeGitHubApp, + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } profile.Reviewer = ProfileReviewer{ Kind: ProfileReviewerKindEntity, @@ -989,9 +986,9 @@ func TestPinnedGitHubAppInstallationIDForGit(t *testing.T) { }, } git := GitConfig{ - Host: "github.com", - AuthMode: GitAuthModeGitHubApp, - CredentialRef: "codereview/work-reviewer", + Host: "github.com", + AuthMode: GitAuthModeGitHubApp, + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, } if got := PinnedGitHubAppInstallationIDForGit(profile, git); got != "123456" { t.Fatalf("PinnedGitHubAppInstallationIDForGit = %q, want 123456", got) @@ -1003,7 +1000,7 @@ func TestPinnedGitHubAppInstallationIDForGit(t *testing.T) { t.Fatalf("discover installation id = %q, want empty", got) } - git.CredentialRef = "codereview/work" + git.Credential.Name = "codereview/work" if got := PinnedGitHubAppInstallationIDForGit(profile, git); got != "" { t.Fatalf("unmatched credential installation id = %q, want empty", got) } @@ -1471,7 +1468,6 @@ func TestValidateCredentialLocations(t *testing.T) { profile := cfg.Profiles[profileName] access := cfg.RepositoryAccess[profile.RepositoryAccess] access.Git.Credential = location - access.Git.CredentialRef = "" cfg.RepositoryAccess[profile.RepositoryAccess] = access } @@ -1541,7 +1537,6 @@ func TestValidateCredentialLocations(t *testing.T) { setProfileRepositoryAccessCredential(cfg, "work", CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/shared"}) entity := cfg.ReviewerEntities["work-reviewer"] entity.Credential = CredentialLocation{Store: "work-file", Name: "codereview/shared"} - entity.CredentialRef = "" cfg.ReviewerEntities["work-reviewer"] = entity }, }, @@ -1551,7 +1546,6 @@ func TestValidateCredentialLocations(t *testing.T) { setProfileRepositoryAccessCredential(cfg, "work", CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/shared"}) entity := cfg.ReviewerEntities["work-reviewer"] entity.Credential = CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/shared"} - entity.CredentialRef = "" cfg.ReviewerEntities["work-reviewer"] = entity }, wantErr: ErrInvalid, @@ -2152,7 +2146,6 @@ func TestSubscriptionLLMCredentialsAreAdapterManaged(t *testing.T) { func TestAPIKeyLLMRequiresCredentialRef(t *testing.T) { cfg := validFile() llm := cfg.LLMRuntimes["work-llm"] - llm.CredentialRef = "" llm.Credential = CredentialLocation{} cfg.LLMRuntimes["work-llm"] = llm @@ -2168,12 +2161,11 @@ func TestValidateRejectsInvalidCredentialRefs(t *testing.T) { }{ {name: "subscription LLM stored ref", mutate: func(cfg *File) { llm := cfg.LLMRuntimes["home-llm"] - llm.CredentialRef = "codereview/home-llm" + llm.Credential = CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/home-llm"} cfg.LLMRuntimes["home-llm"] = llm }}, {name: "empty reviewer credential ref", mutate: func(cfg *File) { entity := cfg.ReviewerEntities["work-reviewer"] - entity.CredentialRef = "" entity.Credential = CredentialLocation{} cfg.ReviewerEntities["work-reviewer"] = entity }}, @@ -2181,34 +2173,34 @@ func TestValidateRejectsInvalidCredentialRefs(t *testing.T) { profile := cfg.Profiles["work"] cfg.Profiles["work"] = profile entity := cfg.ReviewerEntities["work-reviewer"] - entity.CredentialRef = profile.Git.CredentialRef + entity.Credential = profile.Git.Credential cfg.ReviewerEntities["work-reviewer"] = entity }}, {name: "llm credential ref matches git credential ref", mutate: func(cfg *File) { profile := cfg.Profiles["work"] llm := cfg.LLMRuntimes["work-llm"] - llm.CredentialRef = profile.Git.CredentialRef + llm.Credential = profile.Git.Credential cfg.LLMRuntimes["work-llm"] = llm }}, {name: "llm credential ref matches reviewer credential ref", mutate: func(cfg *File) { entity := cfg.ReviewerEntities["work-reviewer"] llm := cfg.LLMRuntimes["work-llm"] - llm.CredentialRef = entity.CredentialRef + llm.Credential = entity.Credential cfg.LLMRuntimes["work-llm"] = llm }}, {name: "git ref invalid chars", mutate: func(cfg *File) { profile := cfg.Profiles["home"] - profile.Git.CredentialRef = "codereview/bad.profile" + profile.Git.Credential.Name = "codereview/bad.profile" cfg.Profiles["home"] = profile }}, {name: "git ref wrong service", mutate: func(cfg *File) { profile := cfg.Profiles["home"] - profile.Git.CredentialRef = "other/home" + profile.Git.Credential.Name = "other/home" cfg.Profiles["home"] = profile }}, {name: "llm ref invalid chars", mutate: func(cfg *File) { llm := cfg.LLMRuntimes["work-llm"] - llm.CredentialRef = "codereview/work.llm" + llm.Credential.Name = "codereview/work.llm" cfg.LLMRuntimes["work-llm"] = llm }}, } @@ -2380,17 +2372,17 @@ func validFile() File { Adapter: LLMAdapterClaudeCLI, }, "work-llm": { - Provider: LLMProviderAnthropic, - Auth: LLMAuthAPIKey, - Adapter: LLMAdapterAnthropicAPI, - CredentialRef: "codereview/work-llm", + Provider: LLMProviderAnthropic, + Auth: LLMAuthAPIKey, + Adapter: LLMAdapterAnthropicAPI, + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, }, ReviewerEntities: map[string]ReviewerEntity{ "work-reviewer": { Host: "github.com", AuthMode: GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, DisplayName: "Work reviewer bot", IdentityCache: "acme-review-bot", }, @@ -2400,7 +2392,7 @@ func validFile() File { Git: GitConfig{ Host: "github.com", AuthMode: GitAuthModePAT, - CredentialRef: "codereview/home", + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/home"}, IdentityCache: "rianjs", }, Reviewer: ProfileReviewer{Kind: ProfileReviewerKindGitIdentity}, @@ -2415,7 +2407,7 @@ func validFile() File { Git: GitConfig{ Host: "github.com", AuthMode: GitAuthModePAT, - CredentialRef: "codereview/work", + Credential: CredentialLocation{Store: LocalOSCredentialStoreID, Name: "codereview/work"}, IdentityCache: "rianjs", }, Reviewer: ProfileReviewer{Kind: ProfileReviewerKindEntity, Entity: "work-reviewer"}, diff --git a/internal/config/configtest/configtest.go b/internal/config/configtest/configtest.go index 75fc3e4..e9b9059 100644 --- a/internal/config/configtest/configtest.go +++ b/internal/config/configtest/configtest.go @@ -30,10 +30,9 @@ func File(opts ...Option) config.File { Profiles: map[string]config.Profile{ "home": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - Credential: config.CredentialLocation{Store: "test-memory"}, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, diff --git a/internal/configedit/configedit_test.go b/internal/configedit/configedit_test.go index 110d69c..48d46d7 100644 --- a/internal/configedit/configedit_test.go +++ b/internal/configedit/configedit_test.go @@ -399,16 +399,16 @@ func TestRenameProfileUpdatesRoutesAndPreservesProfileValues(t *testing.T) { t.Fatal("old profile still exists after rename") } office := got.Profiles["office"] - if office.Git.CredentialRef != "codereview/custom-work" || office.Git.IdentityCache != "work-user" { + if office.Git.Credential.Name != "codereview/custom-work" || office.Git.IdentityCache != "work-user" { t.Fatalf("git fields = %#v, want preserved credential ref and identity cache", office.Git) } if office.ReviewerCredentials == nil || - office.ReviewerCredentials.CredentialRef != "codereview/custom-reviewer" || + office.ReviewerCredentials.Credential.Name != "codereview/custom-reviewer" || office.ReviewerCredentials.IdentityCache != "review-bot" { t.Fatalf("reviewer credentials = %#v, want preserved refs/cache", office.ReviewerCredentials) } - if office.LLM.CredentialRef != "codereview/custom-llm" { - t.Fatalf("llm credential ref = %q, want preserved custom ref", office.LLM.CredentialRef) + if office.LLM.Credential.Name != "codereview/custom-llm" { + t.Fatalf("llm credential ref = %q, want preserved custom ref", office.LLM.Credential.Name) } wantRoutes := []config.RepositoryProfile{ { @@ -701,9 +701,9 @@ func testConfig() config.File { ), configtest.HomeProfile(config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -715,19 +715,19 @@ func testConfig() config.File { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-work", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-work"}, IdentityCache: "work-user", }, ReviewerCredentials: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/custom-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-reviewer"}, IdentityCache: "review-bot", }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/custom-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/custom-llm"}, }, AgentSources: []string{"~/agents"}, }), diff --git a/internal/credentials/credentials.go b/internal/credentials/credentials.go index 58fae4c..3079ff8 100644 --- a/internal/credentials/credentials.go +++ b/internal/credentials/credentials.go @@ -312,9 +312,6 @@ func ResolveCredentialStore(cfg config.File, storeID string) (ResolvedSecretsSto // ResolveSecretsStoreForProfile resolves the effective credential store for // one review profile. func ResolveSecretsStoreForProfile(cfg config.File, profile config.Profile) (ResolvedSecretsStore, error) { - if selection := strings.TrimSpace(profile.SecretsStore); selection != "" { - return ResolveCredentialStore(cfg, selection) - } profile = config.Normalize(config.File{ Profiles: map[string]config.Profile{"profile": profile}, }).Profiles["profile"] @@ -778,7 +775,7 @@ func ExpectedKeysForConfigRef(cfg config.File, ref string) ([]string, error) { func matchingCredentialRefs(profile config.Profile, ref string) []config.CredentialRef { refs := []config.CredentialRef{} - gitCredential := normalizedCredentialLocation(profile.Git.Credential, profile.Git.CredentialRef) + gitCredential := profile.Git.Credential if gitCredential.Name == ref { refs = append(refs, config.CredentialRef{ Purpose: "git", @@ -788,7 +785,7 @@ func matchingCredentialRefs(profile config.Profile, ref string) []config.Credent }) } if profile.ReviewerCredentials != nil { - reviewerCredential := normalizedCredentialLocation(profile.ReviewerCredentials.Credential, profile.ReviewerCredentials.CredentialRef) + reviewerCredential := profile.ReviewerCredentials.Credential if reviewerCredential.Name == ref { refs = append(refs, config.CredentialRef{ Purpose: "reviewer_credentials", @@ -798,7 +795,7 @@ func matchingCredentialRefs(profile config.Profile, ref string) []config.Credent }) } } - llmCredential := normalizedCredentialLocation(profile.LLM.Credential, profile.LLM.CredentialRef) + llmCredential := profile.LLM.Credential if profile.LLM.Auth == config.LLMAuthAPIKey && llmCredential.Name == ref { refs = append(refs, config.CredentialRef{ Purpose: "llm", @@ -811,19 +808,6 @@ func matchingCredentialRefs(profile config.Profile, ref string) []config.Credent return refs } -func normalizedCredentialLocation(location config.CredentialLocation, legacyRef string) config.CredentialLocation { - location.Store = strings.TrimSpace(location.Store) - location.Name = strings.TrimSpace(location.Name) - legacyRef = strings.TrimSpace(legacyRef) - if legacyRef != "" { - if location.Store == "" { - location.Store = config.LocalOSCredentialStoreID - } - location.Name = legacyRef - } - return location -} - // ValidateAllowedKey fails when key is not in cr's write allowlist. func ValidateAllowedKey(key string) error { allowed := AllowedKeys() diff --git a/internal/credentials/credentials_test.go b/internal/credentials/credentials_test.go index aac7afc..8517e6a 100644 --- a/internal/credentials/credentials_test.go +++ b/internal/credentials/credentials_test.go @@ -492,7 +492,7 @@ func githubAppMatrixProfile(ref string) config.Profile { p.Git.GitHubApp = &config.GitHubAppConfig{AppID: "12345"} p.LLM.Auth = config.LLMAuthSubscription p.LLM.Adapter = config.LLMAdapterClaudeCLI - p.LLM.CredentialRef = "" + p.LLM.Credential = config.CredentialLocation{} return p } @@ -503,9 +503,9 @@ func TestExpectedKeysForConfigRefIgnoresUnrelatedUnsupportedProfiles(t *testing. "shared-pat": matrixProfile("codereview/shared-git", "codereview/shared-llm", config.LLMProviderOpenAI), "future": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeOAuthDevice, - CredentialRef: "codereview/future", // #nosec G101 -- keyring ref, not a secret value. + Host: "github.com", + AuthMode: config.GitAuthModeOAuthDevice, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/future"}, // #nosec G101 -- keyring ref, not a secret value. }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -515,9 +515,9 @@ func TestExpectedKeysForConfigRefIgnoresUnrelatedUnsupportedProfiles(t *testing. }, "shared-future": { Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeOAuthDevice, - CredentialRef: "codereview/shared-git", + Host: "github.com", + AuthMode: config.GitAuthModeOAuthDevice, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/shared-git"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -953,15 +953,15 @@ func matrixProfile(gitRef, llmRef string, provider config.LLMProvider) config.Pr } return config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: gitRef, + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: gitRef}, }, LLM: config.LLMConfig{ - Provider: provider, - Auth: config.LLMAuthAPIKey, - Adapter: adapter, - CredentialRef: llmRef, + Provider: provider, + Auth: config.LLMAuthAPIKey, + Adapter: adapter, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: llmRef}, }, } } diff --git a/internal/gitprovider/github/app_auth_test.go b/internal/gitprovider/github/app_auth_test.go index 79223bd..0825184 100644 --- a/internal/gitprovider/github/app_auth_test.go +++ b/internal/gitprovider/github/app_auth_test.go @@ -347,10 +347,10 @@ func githubAppGitConfig(ref string) config.GitConfig { func githubAppGitConfigWithAppID(ref string, appID string) config.GitConfig { return config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeGitHubApp, - GitHubApp: &config.GitHubAppConfig{AppID: appID}, - CredentialRef: ref, + Host: "github.com", + AuthMode: config.GitAuthModeGitHubApp, + GitHubApp: &config.GitHubAppConfig{AppID: appID}, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: ref}, } } diff --git a/internal/gitprovider/github/client.go b/internal/gitprovider/github/client.go index 53edb50..161df5c 100644 --- a/internal/gitprovider/github/client.go +++ b/internal/gitprovider/github/client.go @@ -74,7 +74,7 @@ func NewFromGitConfig(git config.GitConfig, store credentials.Reader, opts Optio if store == nil { return nil, gitprovider.Credential{}, fmt.Errorf("%w: token store is required", gitprovider.ErrAuth) } - parsed, err := credentials.ParseRef(git.CredentialRef) + parsed, err := credentials.ParseRef(git.Credential.Name) if err != nil { return nil, gitprovider.Credential{}, err } @@ -83,7 +83,7 @@ func NewFromGitConfig(git config.GitConfig, store credentials.Reader, opts Optio case config.GitAuthModePAT: key, err := credentials.KeyForPurpose(config.CredentialRef{ Purpose: "git", - Ref: git.CredentialRef, + Ref: git.Credential.Name, Mode: string(git.AuthMode), }) if err != nil { diff --git a/internal/gitprovider/github/client_test.go b/internal/gitprovider/github/client_test.go index 30efc9e..44e2b25 100644 --- a/internal/gitprovider/github/client_test.go +++ b/internal/gitprovider/github/client_test.go @@ -34,9 +34,9 @@ func TestCapabilities(t *testing.T) { func TestNewFromGitConfigBuildsPATClientAndCredential(t *testing.T) { store := tokenStore{"work": {credentials.GitTokenKey: "token"}} client, credential, err := NewFromGitConfig(config.GitConfig{ - Host: "github.example.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.example.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, store, Options{}) if err != nil { t.Fatalf("NewFromGitConfig: %v", err) @@ -61,9 +61,9 @@ func TestNewFromGitConfigPATUsesCachingReaderAcrossRepeatedReads(t *testing.T) { }} reader := credentials.CachingReader("git-store", base) cfg := config.GitConfig{ - Host: "github.example.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.example.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, } first, credential, err := NewFromGitConfig(cfg, reader, Options{}) @@ -92,18 +92,18 @@ func TestNewRequiresExplicitHost(t *testing.T) { func TestNewFromGitConfigRejectsReservedAuthModesAndHostConflict(t *testing.T) { store := tokenStore{"work": {credentials.GitTokenKey: "token"}} _, _, err := NewFromGitConfig(config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModeOAuthDevice, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModeOAuthDevice, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, store, Options{}) if !errors.Is(err, config.ErrUnsupported) { t.Fatalf("NewFromGitConfig(oauth_device) error = %v, want ErrUnsupported", err) } _, _, err = NewFromGitConfig(config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, store, Options{Host: "github.example.com"}) if !errors.Is(err, ErrValidation) { t.Fatalf("NewFromGitConfig host conflict error = %v, want ErrValidation", err) @@ -112,9 +112,9 @@ func TestNewFromGitConfigRejectsReservedAuthModesAndHostConflict(t *testing.T) { func TestNewFromGitConfigRejectsMissingTokenWithoutLeaking(t *testing.T) { _, _, err := NewFromGitConfig(config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, }, tokenStore{"work": {}}, Options{}) if !errors.Is(err, gitprovider.ErrAuth) { t.Fatalf("NewFromGitConfig missing token error = %v, want ErrAuth", err) diff --git a/internal/identity/identity.go b/internal/identity/identity.go index c35d2fd..829161b 100644 --- a/internal/identity/identity.go +++ b/internal/identity/identity.go @@ -155,7 +155,6 @@ func reviewerIdentitySource(profile config.Profile) (config.GitConfig, string) { AuthMode: profile.ReviewerCredentials.AuthMode, Credential: profile.ReviewerCredentials.Credential, GitHubApp: githubApp, - CredentialRef: profile.ReviewerCredentials.CredentialRef, IdentityCache: profile.ReviewerCredentials.IdentityCache, }, profile.ReviewerCredentials.IdentityCache } diff --git a/internal/identity/identity_test.go b/internal/identity/identity_test.go index 96a27e5..49eac4d 100644 --- a/internal/identity/identity_test.go +++ b/internal/identity/identity_test.go @@ -29,7 +29,7 @@ func TestRefreshUsesGitCredentialsAndUpdatesCache(t *testing.T) { if len(results) != 1 || results[0].CredentialSource != SourceGit || !results[0].IdentityCacheUpdated { t.Fatalf("results = %#v, want one git update", results) } - if len(resolver.calls) != 1 || resolver.calls[0].CredentialRef != "codereview/home" { + if len(resolver.calls) != 1 || resolver.calls[0].Credential.Name != "codereview/home" { t.Fatalf("resolver calls = %#v, want git ref", resolver.calls) } } @@ -57,7 +57,7 @@ func TestRefreshUsesReviewerCredentialsWhenPresent(t *testing.T) { if len(results) != 1 || results[0].CredentialSource != SourceReviewer || results[0].PreviousIdentityCache != "old-bot" { t.Fatalf("results = %#v, want reviewer result with old cache", results) } - if len(resolver.calls) != 1 || resolver.calls[0].Host != "github.com" || resolver.calls[0].CredentialRef != "codereview/work-reviewer" { + if len(resolver.calls) != 1 || resolver.calls[0].Host != "github.com" || resolver.calls[0].Credential.Name != "codereview/work-reviewer" { t.Fatalf("resolver calls = %#v, want reviewer ref on profile host", resolver.calls) } } @@ -85,9 +85,9 @@ func TestRefreshAllSortedAndAtomicOnFailure(t *testing.T) { t.Fatalf("returned home cache = %q, want original", got) } if len(resolver.calls) != 3 || - resolver.calls[0].CredentialRef != "codereview/home" || - resolver.calls[1].CredentialRef != "codereview/work" || - resolver.calls[2].CredentialRef != "codereview/work-reviewer" { + resolver.calls[0].Credential.Name != "codereview/home" || + resolver.calls[1].Credential.Name != "codereview/work" || + resolver.calls[2].Credential.Name != "codereview/work-reviewer" { t.Fatalf("resolver calls = %#v, want sorted home then work git/reviewer", resolver.calls) } } @@ -99,12 +99,12 @@ func TestRefreshAllReviewerCredentialsRollbackOnFailure(t *testing.T) { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/reviewer-git", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/reviewer-git"}, IdentityCache: "git-cache", }, ReviewerCredentials: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/reviewer", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/reviewer"}, IdentityCache: "old-bot", }, }, @@ -112,7 +112,7 @@ func TestRefreshAllReviewerCredentialsRollbackOnFailure(t *testing.T) { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/failing", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/failing"}, IdentityCache: "old-failing", }, }, @@ -145,9 +145,9 @@ func TestRefreshAllReviewerCredentialsRollbackOnFailure(t *testing.T) { t.Fatalf("input reviewer cache = %q, want original", got) } if len(resolver.calls) != 3 || - resolver.calls[0].CredentialRef != "codereview/reviewer-git" || - resolver.calls[1].CredentialRef != "codereview/reviewer" || - resolver.calls[2].CredentialRef != "codereview/failing" { + resolver.calls[0].Credential.Name != "codereview/reviewer-git" || + resolver.calls[1].Credential.Name != "codereview/reviewer" || + resolver.calls[2].Credential.Name != "codereview/failing" { t.Fatalf("resolver calls = %#v, want reviewer git/reviewer then failing", resolver.calls) } } @@ -230,10 +230,10 @@ type fakeResolver struct { func (f *fakeResolver) ResolveIdentity(_ context.Context, _ string, git config.GitConfig) (gitprovider.Identity, error) { f.calls = append(f.calls, git) - if err := f.errs[git.CredentialRef]; err != nil { + if err := f.errs[git.Credential.Name]; err != nil { return gitprovider.Identity{}, err } - return f.identities[git.CredentialRef], nil + return f.identities[git.Credential.Name], nil } func testConfig() config.File { @@ -244,7 +244,7 @@ func testConfig() config.File { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/home"}, IdentityCache: "old-home", }, LLM: config.LLMConfig{ @@ -258,12 +258,12 @@ func testConfig() config.File { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work"}, IdentityCache: "work-user-cache", }, ReviewerCredentials: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: "test-memory", Name: "codereview/work-reviewer"}, IdentityCache: "old-bot", }, LLM: config.LLMConfig{ diff --git a/internal/llmadapters/api.go b/internal/llmadapters/api.go index 60a719e..1c127a8 100644 --- a/internal/llmadapters/api.go +++ b/internal/llmadapters/api.go @@ -73,13 +73,13 @@ func NewAPIAdapterFromConfig(llmConfig config.LLMConfig, store credentials.Reade if store == nil { return nil, fmt.Errorf("%w: token store is required", ErrAPIAdapterConfig) } - parsed, err := credentials.ParseRef(llmConfig.CredentialRef) + parsed, err := credentials.ParseRef(llmConfig.Credential.Name) if err != nil { return nil, err } key, err := credentials.KeyForPurpose(config.CredentialRef{ Purpose: "llm", - Ref: llmConfig.CredentialRef, + Ref: llmConfig.Credential.Name, Mode: string(llmConfig.Auth), Provider: string(llmConfig.Provider), }) @@ -88,7 +88,7 @@ func NewAPIAdapterFromConfig(llmConfig config.LLMConfig, store credentials.Reade } apiKey, err := store.Get(parsed.Profile, key) if err != nil { - return nil, fmt.Errorf("%w: read llm credential %s/%s: %w", ErrAPIAdapterConfig, llmConfig.CredentialRef, key, err) + return nil, fmt.Errorf("%w: read llm credential %s/%s: %w", ErrAPIAdapterConfig, llmConfig.Credential.Name, key, err) } opts.APIKey = apiKey return newAPIAdapter(kind, opts) diff --git a/internal/llmadapters/api_test.go b/internal/llmadapters/api_test.go index 70ca5e2..42a4f70 100644 --- a/internal/llmadapters/api_test.go +++ b/internal/llmadapters/api_test.go @@ -218,10 +218,10 @@ func TestAPIAdapterFromConfig(t *testing.T) { { name: "anthropic", cfg: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, apiKey: "stored-value", want: "anthropic_api", @@ -231,10 +231,10 @@ func TestAPIAdapterFromConfig(t *testing.T) { { name: "openai", cfg: config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, apiKey: "openai-stored-value", want: "openai_api", @@ -264,10 +264,10 @@ func TestAPIAdapterFromConfig(t *testing.T) { "work-llm": {credentials.LegacyLLMAPIKeyKey: "stored-value"}, }} _, err := NewAPIAdapterFromConfig(config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, store, APIOptions{}) if !errors.Is(err, ErrAPIAdapterConfig) { t.Fatalf("NewAPIAdapterFromConfig error = %v, want ErrAPIAdapterConfig", err) @@ -295,10 +295,10 @@ func TestAPIAdapterFromConfig(t *testing.T) { { name: "provider mismatch refused", cfg: config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, }, } { @@ -313,18 +313,18 @@ func TestAPIAdapterFromConfig(t *testing.T) { } if _, err := NewAPIAdapterFromConfig(config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, nil, APIOptions{}); !errors.Is(err, ErrAPIAdapterConfig) { t.Fatalf("nil store error = %v, want ErrAPIAdapterConfig", err) } if _, err := NewAPIAdapterFromConfig(config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/missing", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/missing"}, }, nil, APIOptions{}); !errors.Is(err, ErrAPIAdapterConfig) || !strings.Contains(err.Error(), "requires provider anthropic") { t.Fatalf("mismatch with nil store error = %v, want provider/adapter validation", err) } @@ -336,10 +336,10 @@ func TestAPIAdapterFromConfigUsesCachingReaderAcrossRepeatedReads(t *testing.T) }} reader := credentials.CachingReader("llm-store", base) cfg := config.LLMConfig{ - Provider: config.LLMProviderOpenAI, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterOpenAIAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderOpenAI, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterOpenAIAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, } first, err := NewAPIAdapterFromConfig(cfg, reader, APIOptions{BaseURL: "https://example.invalid"}) diff --git a/internal/pipeline/pipeline_test.go b/internal/pipeline/pipeline_test.go index 9a79585..1b36836 100644 --- a/internal/pipeline/pipeline_test.go +++ b/internal/pipeline/pipeline_test.go @@ -4022,7 +4022,7 @@ func TestDryRunMarksRunFailedAfterPostAllocationError(t *testing.T) { func TestDryRunRejectsSelfReviewWhenReviewerCredentialsMatchAuthor(t *testing.T) { provider, req := dryRunHarness(t) - req.Profile.ReviewerCredentials = &config.ReviewerCredentials{AuthMode: config.GitAuthModePAT, CredentialRef: "codereview/reviewer"} + req.Profile.ReviewerCredentials = &config.ReviewerCredentials{AuthMode: config.GitAuthModePAT, Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/reviewer"}} req.PostingIdentity = provider.pr.Author adapter := &llm.FakeAdapter{QuotaErr: errors.New("quota should not be called")} @@ -4937,9 +4937,9 @@ func namedSessionForRequest(req Request, providerSessionID string) ledger.NamedS func testProfile(agentSource string) config.Profile { profile := config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, diff --git a/internal/view/config.go b/internal/view/config.go index a1b0617..4c9ac9a 100644 --- a/internal/view/config.go +++ b/internal/view/config.go @@ -78,9 +78,9 @@ type LLMCredential struct { func NewConfigShow(profileName string, profile config.Profile, data config.DataConfig, refs []CredentialStatus) ConfigShow { llmCredential := LLMCredential{Mode: "adapter_managed"} if profile.LLM.Auth == config.LLMAuthAPIKey { - llmCredential = LLMCredential{Mode: "stored_ref", Ref: profile.LLM.CredentialRef} + llmCredential = LLMCredential{Mode: "stored_ref", Ref: profile.LLM.Credential.Name} } - credentialRef := profile.Git.CredentialRef + credentialRef := profile.Git.Credential.Name return ConfigShow{ ActiveProfile: profileName, Profile: profile, @@ -137,7 +137,7 @@ func RenderConfigText(w io.Writer, show ConfigShow) error { if err := writeKV(w, " Auth mode", string(show.Profile.Git.AuthMode)); err != nil { return err } - if err := writeKV(w, " Credential name", show.Profile.Git.CredentialRef); err != nil { + if err := writeKV(w, " Credential name", show.Profile.Git.Credential.Name); err != nil { return err } if err := writeOptionalKV(w, " Identity cache", show.Profile.Git.IdentityCache); err != nil { @@ -155,7 +155,7 @@ func RenderConfigText(w io.Writer, show ConfigShow) error { if err := writeKV(w, " Auth mode", string(show.Profile.ReviewerCredentials.AuthMode)); err != nil { return err } - if err := writeKV(w, " Credential name", show.Profile.ReviewerCredentials.CredentialRef); err != nil { + if err := writeKV(w, " Credential name", show.Profile.ReviewerCredentials.Credential.Name); err != nil { return err } if err := writeOptionalKV(w, " Identity cache", show.Profile.ReviewerCredentials.IdentityCache); err != nil { diff --git a/internal/view/config_test.go b/internal/view/config_test.go index 5ff2e20..2c4a12c 100644 --- a/internal/view/config_test.go +++ b/internal/view/config_test.go @@ -736,9 +736,9 @@ func TestRenderConfigClearTextIncludesCacheErrorWithoutPath(t *testing.T) { func homeProfile() config.Profile { return config.Profile{ Git: config.GitConfig{ - Host: "github.com", - AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/home", + Host: "github.com", + AuthMode: config.GitAuthModePAT, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/home"}, }, LLM: config.LLMConfig{ Provider: config.LLMProviderAnthropic, @@ -772,19 +772,19 @@ func workProfile() config.Profile { Git: config.GitConfig{ Host: "github.com", AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work"}, IdentityCache: "rianjs", }, ReviewerCredentials: &config.ReviewerCredentials{ AuthMode: config.GitAuthModePAT, - CredentialRef: "codereview/work-reviewer", + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-reviewer"}, IdentityCache: "acme-review-bot", }, LLM: config.LLMConfig{ - Provider: config.LLMProviderAnthropic, - Auth: config.LLMAuthAPIKey, - Adapter: config.LLMAdapterAnthropicAPI, - CredentialRef: "codereview/work-llm", + Provider: config.LLMProviderAnthropic, + Auth: config.LLMAuthAPIKey, + Adapter: config.LLMAdapterAnthropicAPI, + Credential: config.CredentialLocation{Store: config.LocalOSCredentialStoreID, Name: "codereview/work-llm"}, }, AgentSources: []string{"~/dev/work-reviewers"}, ReviewPolicy: config.ReviewPolicy{