Add per-provider auth.json keys; make auth methods registry-generic (P0b)

Platform API keys now live in auth.json under the platform-id scope (the
per-provider auth.json key contract), resolved env > auth.json > legacy
[platforms.*] config.toml (read-only fallback). The TUI login picker,
paste box, auth-method advertising, and authenticate handler are all
registry-generic: a new PlatformSpec row appears in the login UI and
authenticates with zero UI changes. Spec rows gained vendor/console_host/
login_label display fields (moonshot strings byte-identical, pinned by
tests).

Adversarial review caught that auth.json keys were validated at login but
never stamped onto catalog entries (completions would 401; restart lost
eager auth). Fixed red-green: resolve_model_list/resolve_model_catalog now
take a resolved PlatformApiKeys snapshot consumed by the credential-
stamping layer (auth.json beats stale config.toml, matching the login
validator), with production callers resolving fresh per catalog build.
Also from review: the new auth.json writer takes the manager's cross-
process flock (bounded retry — an unlocked RMW racing a token refresh
could revert a rotated refresh token); the oauth-401 wiremock test is
hermetic (KIGI_SHARE_DIR tempdir; it could read a dev's real auth.json
and hit live moonshot); cli_models resolves real keys; auth.json is read
once per registry sweep; caller-less lock_config_writes deleted; catalog
resolvers tightened to pub(crate); stale config.toml doc comments and the
no-credentials error copy updated.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-21 01:18:46 -04:00
1 parent 99d99fb47a
commit c5ddaec71e
19 files changed
+731 -375

No files matched your search

@@ -112,14 +112,14 @@ fn submit_platform_api_key_dispatches_persist_then_authenticate() {
app.auth_state = AuthState::Pending { error: None };
let effects = dispatch(
Action::BeginPlatformKeyEntry(PlatformLogin::MoonshotCn),
Action::BeginPlatformKeyEntry(PlatformLogin(kigi_shell::models::PlatformId::MoonshotCn)),
&mut app,
);
assert!(effects.is_empty(), "entering key entry is UI-only");
let seq = match &app.auth_state {
AuthState::Authenticating {
request_seq,
mode: AuthMode::ApiKeyEntry(PlatformLogin::MoonshotCn),
mode: AuthMode::ApiKeyEntry(PlatformLogin(kigi_shell::models::PlatformId::MoonshotCn)),
..
} => *request_seq,
other => panic!("expected ApiKeyEntry(MoonshotCn), got {other:?}"),
@@ -135,7 +135,10 @@ fn submit_platform_api_key_dispatches_persist_then_authenticate() {
},
] => {
assert_eq!(*request_seq, seq);
assert_eq!(*target, PlatformLogin::MoonshotCn);
assert_eq!(
*target,
PlatformLogin(kigi_shell::models::PlatformId::MoonshotCn)
);
assert_eq!(key, "sk-test-key");
assert_eq!(
target.method_id().0.as_ref(),
@@ -179,7 +182,7 @@ fn cancel_platform_key_entry_returns_to_picker() {
let mut app = test_app();
app.auth_state = AuthState::Pending { error: None };
dispatch(
Action::BeginPlatformKeyEntry(PlatformLogin::MoonshotAi),
Action::BeginPlatformKeyEntry(PlatformLogin(kigi_shell::models::PlatformId::MoonshotAi)),
&mut app,
);
app.auth_code_input = "sk-half-typed".into();
@@ -200,7 +203,7 @@ fn submit_platform_api_key_ignores_blank_key() {
let mut app = test_app();
app.auth_state = AuthState::Pending { error: None };
dispatch(
Action::BeginPlatformKeyEntry(PlatformLogin::MoonshotCn),
Action::BeginPlatformKeyEntry(PlatformLogin(kigi_shell::models::PlatformId::MoonshotCn)),
&mut app,
);
let effects = dispatch(Action::SubmitPlatformApiKey(" ".into()), &mut app);
@@ -208,7 +211,7 @@ fn submit_platform_api_key_ignores_blank_key() {
assert!(matches!(
app.auth_state,
AuthState::Authenticating {
mode: AuthMode::ApiKeyEntry(PlatformLogin::MoonshotCn),
mode: AuthMode::ApiKeyEntry(PlatformLogin(kigi_shell::models::PlatformId::MoonshotCn)),
..
}
));