-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix(onboard): match legacy credential migration by canonical alias #10392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
194433c
cbf77c7
0fa40c2
911b477
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -205,4 +205,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<string>(); | ||
| 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Add public-boundary migration coverage. The current tests exercise registration helpers directly rather than the public onboarding entrypoints, so they can pass without proving that alias-aware migration is wired into fresh, resumed, and repair flows. Add coverage that invokes the public onboarding path with an aliased legacy credential and multiple staged credentials, then assert registration succeeds, 📍 Affects 2 files
🤖 Prompt for AI AgentsSource: Path instructions |
||
| 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, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Remove stale dependency fields after the conflict resolution Severity: P1 (blocking). Impact: |
||
| 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 }); | ||
| } | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Handle duplicate staged values before recording migration.
findStagedLegacyKeyreturns the first staged key with the matching value. If multiple staged keys contain the same credential, the result depends on map insertion order. The messaging and provider paths then update only that key, which can leave migration state inconsistent and keepcredentials.jsonon disk.Require a unique value match, or return and record all matching staged keys when identical values are independently verified as migrated. Add regression coverage for duplicate staged values.
🤖 Prompt for AI Agents