From 194433c67ad55ca5c4cdba029985efa3c95f1f0a Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Wed, 26 Aug 2026 17:55:28 +0800 Subject: [PATCH 1/3] fix(onboard): match legacy credential migration by canonical alias Onboard staged legacy credentials.json values under their literal key (e.g. NVIDIA_API_KEY), but provider registration recorded a successful migration only under the exact env key it registered with. When a credential resolves through its canonical alias (NVIDIA_INFERENCE_API_KEY), the migration record and the staged key never matched, so the allStagedMigrated gate in finalization.ts stayed false and the legacy file was kept even though the credential was genuinely migrated and used. Match staged legacy keys against their known canonical/alias relationship before recording a migration, so the mismatch cannot recur for either provider or messaging credential registration. Fixes #10388 Signed-off-by: Jason Ma --- src/lib/credentials/store.ts | 12 ++++ .../credential-provider-registration.test.ts | 45 ++++++++++++ .../credential-provider-registration.ts | 37 +++++++--- ...redential-migration-reconciliation.test.ts | 72 +++++++++++++++++++ 4 files changed, 156 insertions(+), 10 deletions(-) diff --git a/src/lib/credentials/store.ts b/src/lib/credentials/store.ts index 89cbafdbc9f..f1a283ed7bd 100644 --- a/src/lib/credentials/store.ts +++ b/src/lib/credentials/store.ts @@ -203,6 +203,18 @@ function getLegacyCredentialAlias(envName: string): string | null { return null; } +/** + * Legacy env key names that resolve to `envName` via + * {@link getLegacyCredentialAlias} (e.g. `NVIDIA_API_KEY` for + * `NVIDIA_INFERENCE_API_KEY`). Staging (`stageLegacyCredentialsToEnv`) + * records a credential under its literal legacy key, while providers + * that consume it can register under the canonical key instead — callers + * that match staged keys against a canonical env name need both. + */ +export function getLegacyCredentialAliasKeys(envName: string): readonly string[] { + return LEGACY_CREDENTIAL_ENV_ALIASES[envName] ?? []; +} + /** * Canonical entry point for provider credential resolution (PR #2306). * Resolves an asynchronous in-process override before `process.env`. diff --git a/src/lib/onboard/credential-provider-registration.test.ts b/src/lib/onboard/credential-provider-registration.test.ts index a45635447e4..77e11833da9 100644 --- a/src/lib/onboard/credential-provider-registration.test.ts +++ b/src/lib/onboard/credential-provider-registration.test.ts @@ -276,6 +276,27 @@ describe("credential provider registration", () => { }, ); + it("records migration under the staged legacy alias key when the provider registers with the canonical env name (#10388)", () => { + const session = { stagedCredentialProviders: [] } as unknown as Session; + const runOpenshell = vi.fn(() => ({ status: 0, stdout: "", stderr: "" })); + const deps = registrationDeps(runOpenshell, session); + deps.getCredential = vi.fn(() => "legacy-key"); + deps.stagedLegacyValues = new Map([["NVIDIA_API_KEY", "legacy-key"]]); + const registration = createCredentialProviderRegistration(deps); + + const result = registration.upsertProvider( + "nvidia-build", + "nvidia", + "NVIDIA_INFERENCE_API_KEY", + "https://integrate.api.nvidia.com/v1", + ); + + expect(result).toEqual({ ok: true }); + expect(deps.migratedLegacyKeys.has("NVIDIA_API_KEY")).toBe(true); + expect(deps.migratedLegacyKeys.has("NVIDIA_INFERENCE_API_KEY")).toBe(false); + expect(deps.persistMigratedLegacyKeys).toHaveBeenCalledOnce(); + }); + it("does not record migration when provider registration fails", () => { const session = { stagedCredentialProviders: [] } as unknown as Session; const runOpenshell = vi.fn((args: string[]) => ({ @@ -871,4 +892,28 @@ describe("credential provider registration", () => { expect(deps.migratedLegacyKeys).toEqual(new Set()); expect(deps.persistMigratedLegacyKeys).not.toHaveBeenCalled(); }); + + it("records messaging migration under the staged legacy alias key when the token def uses the canonical env name (#10388)", () => { + const session = { stagedCredentialProviders: [] } as unknown as Session; + const missing = { status: 1, stdout: "", stderr: "not found" }; + const success = { status: 0, stdout: "", stderr: "" }; + const runOpenshell = vi.fn((args: string[]) => + args[1] === "get" ? missing : success, + ); + const deps = registrationDeps(runOpenshell, session); + deps.stagedLegacyValues = new Map([["NVIDIA_API_KEY", "legacy-key"]]); + const registration = createCredentialProviderRegistration(deps); + + registration.upsertMessagingProviders([ + { + name: "alpha-nvidia-bridge", + envKey: "NVIDIA_INFERENCE_API_KEY", + token: "legacy-key", + }, + ]); + + expect(deps.migratedLegacyKeys.has("NVIDIA_API_KEY")).toBe(true); + expect(deps.migratedLegacyKeys.has("NVIDIA_INFERENCE_API_KEY")).toBe(false); + expect(deps.persistMigratedLegacyKeys).toHaveBeenCalledOnce(); + }); }); diff --git a/src/lib/onboard/credential-provider-registration.ts b/src/lib/onboard/credential-provider-registration.ts index 1fd6a7dfa89..f3d49a0345e 100644 --- a/src/lib/onboard/credential-provider-registration.ts +++ b/src/lib/onboard/credential-provider-registration.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getLegacyCredentialAliasKeys } from "../credentials/store"; import type { WebSearchConfig } from "../inference/web-search"; import type { CheckpointProviderBinding } from "../state/onboard-checkpoint-types"; import type { Session } from "../state/onboard-session"; @@ -49,6 +50,20 @@ export interface CredentialProviderRegistrationDeps { persistMigratedLegacyKeys(): void; } +// stagedLegacyValues is keyed by the literal env key found in the legacy +// credentials.json (e.g. NVIDIA_API_KEY), but a provider can consume that +// value under its canonical env key instead (e.g. NVIDIA_INFERENCE_API_KEY). +// Check the canonical key first, then fall back to its known legacy +// aliases, so migration is still recorded against whichever key was +// actually staged. +function findStagedLegacyKey( + envKey: string, + stagedLegacyValues: ReadonlyMap, +): string | undefined { + if (stagedLegacyValues.has(envKey)) return envKey; + return getLegacyCredentialAliasKeys(envKey).find((alias) => stagedLegacyValues.has(alias)); +} + function recordMigratedLegacyMessagingCredentials( tokenDefs: readonly MessagingTokenDef[], registeredProviderNames: readonly string[], @@ -56,18 +71,19 @@ function recordMigratedLegacyMessagingCredentials( revalidatePolicyRequirements?: (operation: string) => void, ): void { const registeredProviders = new Set(registeredProviderNames); - const migrations: Array<{ envKey: string; migrated: boolean }> = []; + const migrations: Array<{ stagedKey: string; migrated: boolean }> = []; for (const def of tokenDefs) { if (!registeredProviders.has(def.name) || !def.token || !def.envKey) continue; - const stagedValue = deps.stagedLegacyValues.get(def.envKey); - if (stagedValue === undefined) continue; - migrations.push({ envKey: def.envKey, migrated: def.token === stagedValue }); + const stagedKey = findStagedLegacyKey(def.envKey, deps.stagedLegacyValues); + if (stagedKey === undefined) continue; + const stagedValue = deps.stagedLegacyValues.get(stagedKey); + migrations.push({ stagedKey, migrated: def.token === stagedValue }); } if (migrations.length === 0) return; revalidatePolicyRequirements?.("record migrated messaging provider credentials"); for (const migration of migrations) { - if (migration.migrated) deps.migratedLegacyKeys.add(migration.envKey); - else deps.migratedLegacyKeys.delete(migration.envKey); + if (migration.migrated) deps.migratedLegacyKeys.add(migration.stagedKey); + else deps.migratedLegacyKeys.delete(migration.stagedKey); } deps.persistMigratedLegacyKeys(); } @@ -169,16 +185,17 @@ export function createCredentialProviderRegistration(deps: CredentialProviderReg options, ); if (result.ok && credentialEnv) { - const stagedValue = deps.stagedLegacyValues.get(credentialEnv); - if (stagedValue !== undefined) { + const stagedKey = findStagedLegacyKey(credentialEnv, deps.stagedLegacyValues); + if (stagedKey !== undefined) { options.revalidatePolicyRequirements?.( `record migrated credential for provider ${JSON.stringify(name)}`, ); + const stagedValue = deps.stagedLegacyValues.get(stagedKey); const upsertedValue = env[credentialEnv] ?? deps.getCredential(credentialEnv); if (upsertedValue === stagedValue) { - deps.migratedLegacyKeys.add(credentialEnv); + deps.migratedLegacyKeys.add(stagedKey); } else { - deps.migratedLegacyKeys.delete(credentialEnv); + deps.migratedLegacyKeys.delete(stagedKey); } deps.persistMigratedLegacyKeys(); } diff --git a/test/credentials/credential-migration-reconciliation.test.ts b/test/credentials/credential-migration-reconciliation.test.ts index 4a99f90c6f6..705d3091e5e 100644 --- a/test/credentials/credential-migration-reconciliation.test.ts +++ b/test/credentials/credential-migration-reconciliation.test.ts @@ -207,4 +207,76 @@ describe("legacy credential reconciliation", () => { fs.rmSync(tmpDir, { recursive: true, force: true }); } }); + + it("removes plaintext when the provider registers the canonical env name for a legacy alias key (#10388)", async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-credential-migration-alias-")); + const legacyDir = path.join(tmpDir, ".nemoclaw"); + const legacyFile = path.join(legacyDir, "credentials.json"); + fs.mkdirSync(legacyDir, { recursive: true, mode: 0o700 }); + fs.writeFileSync(legacyFile, JSON.stringify({ NVIDIA_API_KEY: LEGACY_SECRET }), { + mode: 0o600, + }); + const exit = vi.spyOn(process, "exit").mockImplementation((code) => { + throw new Error(`gateway registration exited ${String(code)}`); + }); + const error = vi.spyOn(console, "error").mockImplementation(() => undefined); + try { + await withProcessEnv( + { HOME: tmpDir, NVIDIA_API_KEY: undefined, NVIDIA_INFERENCE_API_KEY: undefined }, + async () => { + const stagedLegacyKeys = stageLegacyCredentialsToEnv(); + const stagedLegacyValues = new Map( + stagedLegacyKeys.map((key) => [key, process.env[key] ?? ""]), + ); + const migratedLegacyKeys = new Set(); + const session = { stagedCredentialProviders: [] } as unknown as Session; + // Onboarding resolves a staged legacy alias into the canonical env var + // before registering the provider (see credentials/store.ts ensureApiKey()). + process.env.NVIDIA_INFERENCE_API_KEY = process.env.NVIDIA_API_KEY; + const runOpenshell = vi.fn((args: string[]) => ({ + status: args.slice(0, 2).join(" ") === "provider get" ? 1 : 0, + stdout: "", + stderr: "", + })); + const deps: CredentialProviderRegistrationDeps = { + root: path.join(import.meta.dirname, "../.."), + runOpenshell: + runOpenshell as unknown as CredentialProviderRegistrationDeps["runOpenshell"], + redact: (input) => input, + getGatewayName: () => "nemoclaw", + getCredential: (name) => process.env[name] ?? null, + normalizeCredentialValue: (value) => (typeof value === "string" ? value.trim() : ""), + updateSession: (mutator) => mutator(session) ?? session, + stagedLegacyValues, + migratedLegacyKeys, + persistMigratedLegacyKeys: () => undefined, + }; + const registration = createCredentialProviderRegistration(deps); + + const result = registration.upsertProvider( + "nvidia-build", + "nvidia", + "NVIDIA_INFERENCE_API_KEY", + "https://integrate.api.nvidia.com/v1", + { NVIDIA_INFERENCE_API_KEY: process.env.NVIDIA_INFERENCE_API_KEY ?? "" }, + ); + expect(result).toEqual({ ok: true }); + + await finalizeMigration(stagedLegacyKeys, migratedLegacyKeys); + + expect(stagedLegacyKeys).toEqual(["NVIDIA_API_KEY"]); + expect(migratedLegacyKeys.has("NVIDIA_API_KEY")).toBe(true); + expect(exit).not.toHaveBeenCalled(); + expect( + fs.existsSync(legacyFile), + "successful alias-based registration must remove the legacy file", + ).toBe(false); + }, + ); + } finally { + error.mockRestore(); + exit.mockRestore(); + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); }); From cbf77c711d245f162b4afefc4fbd89fb4f1894c1 Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Wed, 26 Aug 2026 18:18:15 +0800 Subject: [PATCH 2/3] fix(onboard): thread legacy alias lookup through deps injection The prior commit imported getLegacyCredentialAliasKeys directly from credentials/store.ts into credential-provider-registration.ts, adding a new static import edge that pushed store.ts's fan-in past the ratcheted architecture budget (46 -> 47) and failed test/repository/source-architecture.test.ts in CI. credential-provider-registration.ts already receives every other store.ts-backed capability (getCredential, normalizeCredentialValue) through CredentialProviderRegistrationDeps rather than importing store.ts directly. Thread getLegacyCredentialAliasKeys through the same deps object instead, wired from onboard.ts, which already imports the whole store.ts module and does not add a new edge. Signed-off-by: Jason Ma --- src/lib/onboard.ts | 2 ++ .../credential-provider-registration.test.ts | 2 ++ .../onboard/credential-provider-registration.ts | 15 ++++++++++++--- .../credential-migration-reconciliation.test.ts | 3 +++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 874a9c89d9f..c1c9fc3b3f0 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -333,6 +333,7 @@ const { normalizeCredentialValue, resolveProviderCredential, saveCredential, + getLegacyCredentialAliasKeys, } = credentials; const { hashCredential, @@ -922,6 +923,7 @@ const registeredCredentialProviders = stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys, + getLegacyCredentialAliasKeys, }); const { upsertProvider, upsertMessagingProviders, providerMatchesGatewayCredential } = registeredCredentialProviders; diff --git a/src/lib/onboard/credential-provider-registration.test.ts b/src/lib/onboard/credential-provider-registration.test.ts index 77e11833da9..24fbe2b31a1 100644 --- a/src/lib/onboard/credential-provider-registration.test.ts +++ b/src/lib/onboard/credential-provider-registration.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest"; +import { getLegacyCredentialAliasKeys } from "../credentials/store"; import type { SandboxMessagingPlan } from "../messaging/manifest"; import type { Session } from "../state/onboard-session"; import { requiredMessagingProviderBindings } from "./checkpoint-replay"; @@ -70,6 +71,7 @@ function registrationDeps( stagedLegacyValues: new Map(), migratedLegacyKeys: new Set(), persistMigratedLegacyKeys: vi.fn(), + getLegacyCredentialAliasKeys, }; } diff --git a/src/lib/onboard/credential-provider-registration.ts b/src/lib/onboard/credential-provider-registration.ts index f3d49a0345e..74881280672 100644 --- a/src/lib/onboard/credential-provider-registration.ts +++ b/src/lib/onboard/credential-provider-registration.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { getLegacyCredentialAliasKeys } from "../credentials/store"; import type { WebSearchConfig } from "../inference/web-search"; import type { CheckpointProviderBinding } from "../state/onboard-checkpoint-types"; import type { Session } from "../state/onboard-session"; @@ -48,6 +47,7 @@ export interface CredentialProviderRegistrationDeps { stagedLegacyValues: ReadonlyMap; migratedLegacyKeys: Set; persistMigratedLegacyKeys(): void; + getLegacyCredentialAliasKeys(envName: string): readonly string[]; } // stagedLegacyValues is keyed by the literal env key found in the legacy @@ -59,6 +59,7 @@ export interface CredentialProviderRegistrationDeps { function findStagedLegacyKey( envKey: string, stagedLegacyValues: ReadonlyMap, + getLegacyCredentialAliasKeys: CredentialProviderRegistrationDeps["getLegacyCredentialAliasKeys"], ): string | undefined { if (stagedLegacyValues.has(envKey)) return envKey; return getLegacyCredentialAliasKeys(envKey).find((alias) => stagedLegacyValues.has(alias)); @@ -74,7 +75,11 @@ function recordMigratedLegacyMessagingCredentials( const migrations: Array<{ stagedKey: string; migrated: boolean }> = []; for (const def of tokenDefs) { if (!registeredProviders.has(def.name) || !def.token || !def.envKey) continue; - const stagedKey = findStagedLegacyKey(def.envKey, deps.stagedLegacyValues); + const stagedKey = findStagedLegacyKey( + def.envKey, + deps.stagedLegacyValues, + deps.getLegacyCredentialAliasKeys, + ); if (stagedKey === undefined) continue; const stagedValue = deps.stagedLegacyValues.get(stagedKey); migrations.push({ stagedKey, migrated: def.token === stagedValue }); @@ -185,7 +190,11 @@ export function createCredentialProviderRegistration(deps: CredentialProviderReg options, ); if (result.ok && credentialEnv) { - const stagedKey = findStagedLegacyKey(credentialEnv, deps.stagedLegacyValues); + const stagedKey = findStagedLegacyKey( + credentialEnv, + deps.stagedLegacyValues, + deps.getLegacyCredentialAliasKeys, + ); if (stagedKey !== undefined) { options.revalidatePolicyRequirements?.( `record migrated credential for provider ${JSON.stringify(name)}`, diff --git a/test/credentials/credential-migration-reconciliation.test.ts b/test/credentials/credential-migration-reconciliation.test.ts index 705d3091e5e..500109775c6 100644 --- a/test/credentials/credential-migration-reconciliation.test.ts +++ b/test/credentials/credential-migration-reconciliation.test.ts @@ -7,6 +7,7 @@ import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { + getLegacyCredentialAliasKeys, removeLegacyCredentialsFile, stageLegacyCredentialsToEnv, } from "../../src/lib/credentials/store.js"; @@ -146,6 +147,7 @@ describe("legacy credential reconciliation", () => { stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys: () => undefined, + getLegacyCredentialAliasKeys, }; const registration = createCredentialProviderRegistration(deps); const tokenDefs: MessagingTokenDef[] = [ @@ -250,6 +252,7 @@ describe("legacy credential reconciliation", () => { stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys: () => undefined, + getLegacyCredentialAliasKeys, }; const registration = createCredentialProviderRegistration(deps); From 0fa40c20d4b2454667a2917709c3042bd66095e5 Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Wed, 26 Aug 2026 18:25:36 +0800 Subject: [PATCH 3/3] fix(onboard): match legacy credential migration by staged value The deps-injection fix in the prior commit kept credential-provider- registration.ts free of a direct credentials/store.ts import, but still grew src/lib/onboard.ts by two lines wiring the new deps field, which failed the onboarding-entry-point net-neutral growth guardrail. Replace the canonical/alias name lookup with a value-based fallback: when a provider's registered env key has no exact match in stagedLegacyValues, search for any staged key whose value equals what was actually registered. This needs no new deps field, no new import, and touches only credential-provider-registration.ts and its tests already added for #10388. Signed-off-by: Jason Ma --- src/lib/credentials/store.ts | 12 ------- src/lib/onboard.ts | 2 -- .../credential-provider-registration.test.ts | 2 -- .../credential-provider-registration.ts | 31 ++++++++----------- ...redential-migration-reconciliation.test.ts | 3 -- 5 files changed, 13 insertions(+), 37 deletions(-) diff --git a/src/lib/credentials/store.ts b/src/lib/credentials/store.ts index f1a283ed7bd..89cbafdbc9f 100644 --- a/src/lib/credentials/store.ts +++ b/src/lib/credentials/store.ts @@ -203,18 +203,6 @@ function getLegacyCredentialAlias(envName: string): string | null { return null; } -/** - * Legacy env key names that resolve to `envName` via - * {@link getLegacyCredentialAlias} (e.g. `NVIDIA_API_KEY` for - * `NVIDIA_INFERENCE_API_KEY`). Staging (`stageLegacyCredentialsToEnv`) - * records a credential under its literal legacy key, while providers - * that consume it can register under the canonical key instead — callers - * that match staged keys against a canonical env name need both. - */ -export function getLegacyCredentialAliasKeys(envName: string): readonly string[] { - return LEGACY_CREDENTIAL_ENV_ALIASES[envName] ?? []; -} - /** * Canonical entry point for provider credential resolution (PR #2306). * Resolves an asynchronous in-process override before `process.env`. diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index c1c9fc3b3f0..874a9c89d9f 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -333,7 +333,6 @@ const { normalizeCredentialValue, resolveProviderCredential, saveCredential, - getLegacyCredentialAliasKeys, } = credentials; const { hashCredential, @@ -923,7 +922,6 @@ const registeredCredentialProviders = stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys, - getLegacyCredentialAliasKeys, }); const { upsertProvider, upsertMessagingProviders, providerMatchesGatewayCredential } = registeredCredentialProviders; diff --git a/src/lib/onboard/credential-provider-registration.test.ts b/src/lib/onboard/credential-provider-registration.test.ts index 24fbe2b31a1..77e11833da9 100644 --- a/src/lib/onboard/credential-provider-registration.test.ts +++ b/src/lib/onboard/credential-provider-registration.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it, vi } from "vitest"; -import { getLegacyCredentialAliasKeys } from "../credentials/store"; import type { SandboxMessagingPlan } from "../messaging/manifest"; import type { Session } from "../state/onboard-session"; import { requiredMessagingProviderBindings } from "./checkpoint-replay"; @@ -71,7 +70,6 @@ function registrationDeps( stagedLegacyValues: new Map(), migratedLegacyKeys: new Set(), persistMigratedLegacyKeys: vi.fn(), - getLegacyCredentialAliasKeys, }; } diff --git a/src/lib/onboard/credential-provider-registration.ts b/src/lib/onboard/credential-provider-registration.ts index 74881280672..78bfbe63929 100644 --- a/src/lib/onboard/credential-provider-registration.ts +++ b/src/lib/onboard/credential-provider-registration.ts @@ -47,22 +47,25 @@ export interface CredentialProviderRegistrationDeps { stagedLegacyValues: ReadonlyMap; migratedLegacyKeys: Set; persistMigratedLegacyKeys(): void; - getLegacyCredentialAliasKeys(envName: string): readonly string[]; } // stagedLegacyValues is keyed by the literal env key found in the legacy // credentials.json (e.g. NVIDIA_API_KEY), but a provider can consume that -// value under its canonical env key instead (e.g. NVIDIA_INFERENCE_API_KEY). -// Check the canonical key first, then fall back to its known legacy -// aliases, so migration is still recorded against whichever key was -// actually staged. +// value under a different env key instead (e.g. NVIDIA_INFERENCE_API_KEY, +// its canonical alias). Match the exact key first, then fall back to any +// staged key whose value equals what the provider actually registered, so +// migration is still recorded against whichever key was actually staged. function findStagedLegacyKey( envKey: string, + value: string | undefined, stagedLegacyValues: ReadonlyMap, - getLegacyCredentialAliasKeys: CredentialProviderRegistrationDeps["getLegacyCredentialAliasKeys"], ): string | undefined { if (stagedLegacyValues.has(envKey)) return envKey; - return getLegacyCredentialAliasKeys(envKey).find((alias) => stagedLegacyValues.has(alias)); + if (value === undefined) return undefined; + for (const [key, stagedValue] of stagedLegacyValues) { + if (stagedValue === value) return key; + } + return undefined; } function recordMigratedLegacyMessagingCredentials( @@ -75,11 +78,7 @@ function recordMigratedLegacyMessagingCredentials( const migrations: Array<{ stagedKey: string; migrated: boolean }> = []; for (const def of tokenDefs) { if (!registeredProviders.has(def.name) || !def.token || !def.envKey) continue; - const stagedKey = findStagedLegacyKey( - def.envKey, - deps.stagedLegacyValues, - deps.getLegacyCredentialAliasKeys, - ); + const stagedKey = findStagedLegacyKey(def.envKey, def.token, deps.stagedLegacyValues); if (stagedKey === undefined) continue; const stagedValue = deps.stagedLegacyValues.get(stagedKey); migrations.push({ stagedKey, migrated: def.token === stagedValue }); @@ -190,17 +189,13 @@ export function createCredentialProviderRegistration(deps: CredentialProviderReg options, ); if (result.ok && credentialEnv) { - const stagedKey = findStagedLegacyKey( - credentialEnv, - deps.stagedLegacyValues, - deps.getLegacyCredentialAliasKeys, - ); + const upsertedValue = env[credentialEnv] ?? deps.getCredential(credentialEnv) ?? undefined; + const stagedKey = findStagedLegacyKey(credentialEnv, upsertedValue, deps.stagedLegacyValues); if (stagedKey !== undefined) { options.revalidatePolicyRequirements?.( `record migrated credential for provider ${JSON.stringify(name)}`, ); const stagedValue = deps.stagedLegacyValues.get(stagedKey); - const upsertedValue = env[credentialEnv] ?? deps.getCredential(credentialEnv); if (upsertedValue === stagedValue) { deps.migratedLegacyKeys.add(stagedKey); } else { diff --git a/test/credentials/credential-migration-reconciliation.test.ts b/test/credentials/credential-migration-reconciliation.test.ts index 500109775c6..705d3091e5e 100644 --- a/test/credentials/credential-migration-reconciliation.test.ts +++ b/test/credentials/credential-migration-reconciliation.test.ts @@ -7,7 +7,6 @@ import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { - getLegacyCredentialAliasKeys, removeLegacyCredentialsFile, stageLegacyCredentialsToEnv, } from "../../src/lib/credentials/store.js"; @@ -147,7 +146,6 @@ describe("legacy credential reconciliation", () => { stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys: () => undefined, - getLegacyCredentialAliasKeys, }; const registration = createCredentialProviderRegistration(deps); const tokenDefs: MessagingTokenDef[] = [ @@ -252,7 +250,6 @@ describe("legacy credential reconciliation", () => { stagedLegacyValues, migratedLegacyKeys, persistMigratedLegacyKeys: () => undefined, - getLegacyCredentialAliasKeys, }; const registration = createCredentialProviderRegistration(deps);