fix(auth): read the Anthropic credential preference from the resolved global dir

Global settings live in settings.json under the resolved global dir, which
falls back to the pre-rename ~/.pi/fusion and ~/.pi/kb dirs for installs that
never migrated. The reader hardcoded ~/.fusion, so on those installs the
operator's preference was silently ignored and resolution fell back to raw-key
precedence -- the same silent fallback the preference exists to remove.

Mirror the legacy-aware lookup getModelRegistryModelsPath already does for
models.json rather than importing core's resolver, which throws under VITEST
when called without an explicit dir.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-24 22:38:15 -07:00
parent 190cc041bc
commit a727e20843
2 changed files with 40 additions and 2 deletions

View File

@@ -131,6 +131,27 @@ describe("Anthropic credential precedence (anthropicAuthPreference)", () => {
await expect(storage.getApiKey("anthropic")).resolves.toBe(SUBSCRIPTION_ACCESS_TOKEN);
});
it("honors the preference stored in a legacy global settings dir", async () => {
writeAuth(homeDir, {
anthropic: { type: "api_key", key: RAW_API_KEY },
"anthropic-subscription": liveSubscriptionCredential(),
});
/*
Operators who never migrated off the pre-rename global dir keep settings.json under
~/.pi/fusion. Reading only ~/.fusion would silently ignore their choice and fall back to
raw-key precedence — the exact silent fallback this preference exists to remove.
*/
mkdirSync(join(homeDir, ".pi", "fusion"), { recursive: true });
writeFileSync(
join(homeDir, ".pi", "fusion", "settings.json"),
JSON.stringify({ anthropicAuthPreference: "subscription" }),
);
const storage = createFusionAuthStorage();
await expect(storage.getApiKey("anthropic")).resolves.toBe(SUBSCRIPTION_ACCESS_TOKEN);
});
it("falls back to the historical precedence when the settings file is unreadable", async () => {
writeAuth(homeDir, {
anthropic: { type: "api_key", key: RAW_API_KEY },

View File

@@ -421,9 +421,26 @@ function resolveStoredCredentialApiKey(providerId: string, credential: StoredCre
* can return them as a fallback when neither Fusion auth nor legacy auth.json
* contains a key for the provider.
*/
/** Global settings file that carries the operator's Anthropic credential preference. */
/*
FNXC:ProviderAuth 2026-07-24-18:20:
Global settings live in `settings.json` under the resolved GLOBAL dir — not in Postgres
(Postgres holds project settings, which are merged over the global ones by
`settings-ops.ts`, and the immutable configuration-revision journal). The global dir is
`~/.fusion` for current installs but falls back to the pre-rename `~/.pi/fusion` and
`~/.pi/kb` dirs for operators who never migrated (see core `resolveGlobalDirForHome`).
Hardcoding `~/.fusion` would silently ignore the operator's preference on those installs and
fall back to raw-key precedence — the same silent-fallback failure this preference exists to
remove. Mirror the legacy-aware lookup `getModelRegistryModelsPath` already does for
models.json rather than importing core's resolver, which throws under VITEST when called
without an explicit dir.
*/
export function getFusionGlobalSettingsPath(home = getHomeDir()): string {
return join(home, ".fusion", "settings.json");
const candidates = [
join(home, ".fusion", "settings.json"),
join(home, ".pi", "fusion", "settings.json"),
join(home, ".pi", "kb", "settings.json"),
];
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
}
/*