#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
pub mod auth_pool_routing;
pub mod auth_pool_state;
#[cfg(unix)]
pub mod custody_storage;
pub mod discovery;
pub mod enrollment;
pub mod interactive;
pub mod lifecycle;
pub mod managed_pool;
#[cfg(target_os = "macos")]
pub mod native_device;
pub mod operations;
pub mod recovery;
pub mod security;
pub mod startup;
pub mod store;
pub fn list_auth_pools() -> Result<
Vec<bcode_provider_auth_models::AuthPoolSummary>,
bcode_provider_auth_models::AuthPoolOperationError,
> {
let config = bcode_config::load_config()
.map_err(|_| bcode_provider_auth_models::AuthPoolOperationError::Configuration)?;
Ok(auth_pool_summaries(&config))
}
pub fn apply_auth_pool_preference(
request: &bcode_provider_auth_models::SetAuthPoolPreferenceRequest,
) -> Result<(), bcode_provider_auth_models::AuthPoolOperationError> {
set_auth_pool_preference(&request.pool, request.profile.as_deref())
.map(|_| ())
.map_err(|_| bcode_provider_auth_models::AuthPoolOperationError::Preference)
}
#[must_use]
pub fn auth_pool_summaries(
config: &bcode_config::BcodeConfig,
) -> Vec<bcode_provider_auth_models::AuthPoolSummary> {
let registry = bcode_config::load_runtime_auth_subscriptions();
let state = auth_pool_state::load_state();
let selected = config.resolved_model_selection();
let names = config
.auth
.pools
.keys()
.chain(registry.pools.keys())
.cloned()
.collect::<BTreeSet<_>>();
names
.into_iter()
.map(|pool| {
let order = bcode_config::effective_auth_pool_order(
config,
®istry,
&pool,
(selected.auth_pool.as_deref() == Some(pool.as_str()))
.then_some(selected.auth_profile.as_deref())
.flatten(),
);
let declared = config.auth.pools.get(&pool);
let runtime = registry.pools.get(&pool);
let source = match order.preference_source.as_deref() {
Some("interactive_state") => {
Some(bcode_provider_auth_models::AuthPoolPreferenceSource::InteractiveState)
}
Some("declarative") => {
Some(bcode_provider_auth_models::AuthPoolPreferenceSource::Declarative)
}
Some("selected_profile") => {
Some(bcode_provider_auth_models::AuthPoolPreferenceSource::SelectedProfile)
}
Some("pool_order") => {
Some(bcode_provider_auth_models::AuthPoolPreferenceSource::PoolOrder)
}
_ => None,
};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_secs());
let profiles = order
.profiles
.iter()
.map(|profile| {
let cooldown_until_unix = state
.entries
.get(&format!("{pool}/{profile}"))
.map(|entry| entry.cooldown_until_unix)
.filter(|until| *until > now);
bcode_provider_auth_models::AuthPoolProfileSummary {
profile: profile.clone(),
preferred: order.preferred_profile.as_deref() == Some(profile.as_str()),
cooldown: cooldown_until_unix.is_some(),
cooldown_until_unix,
}
})
.collect();
bcode_provider_auth_models::AuthPoolSummary {
automatic_group: declared
.and_then(|pool| pool.automatic_group.clone())
.or_else(|| runtime.and_then(|pool| pool.automatic_group.clone())),
schema_version: bcode_provider_auth_models::AUTH_POOL_SCHEMA_VERSION,
pool: pool.clone(),
provider_plugin_id: declared
.and_then(|entry| entry.provider_plugin_id.clone())
.or_else(|| runtime.and_then(|entry| entry.provider_plugin_id.clone())),
strategy: declared.map_or_else(
|| "failover".to_owned(),
|entry| match entry.strategy {
bcode_config::AuthPoolStrategy::Failover => "failover".to_owned(),
bcode_config::AuthPoolStrategy::RoundRobin => "round_robin".to_owned(),
},
),
preferred_profile: order.preferred_profile,
preference_source: source,
profiles,
degraded_reason: order.degraded_reason,
}
})
.collect()
}
pub fn set_auth_pool_preference(
pool: &str,
profile: Option<&str>,
) -> Result<PathBuf, bcode_config::ConfigError> {
let path = bcode_config::set_runtime_auth_pool_preference(pool, profile)?;
auth_pool_state::clear_pool_routing_cursor(Some(pool));
Ok(path)
}
pub fn update_auth_pool_preference(
config: &bcode_config::BcodeConfig,
subscriptions: &mut bcode_config::RuntimeAuthSubscriptions,
routing: &mut auth_pool_state::AuthPoolState,
pool: &str,
profile: Option<&str>,
) -> Result<(), bcode_config::ConfigError> {
bcode_config::update_runtime_auth_pool_preference(config, subscriptions, pool, profile)?;
if let Some(entry) = routing.pools.get_mut(pool) {
entry.last_selected_profile = None;
}
Ok(())
}
use std::time::{SystemTime, UNIX_EPOCH};
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct ProviderRequestContextResolution<'a> {
pub config: &'a bcode_config::BcodeConfig,
pub selection: bcode_config::ResolvedModelSelection,
}
#[must_use]
pub fn resolve_provider_request_context(
request: ProviderRequestContextResolution<'_>,
) -> bcode_model::ProviderRequestContext {
match startup::resolve_remembered_environment(request.config, &request.selection) {
Ok(Some(context)) => return context,
Err(_) => return bcode_model::ProviderRequestContext {
settings: request.selection.settings,
auth_profile: Some("environment".to_owned()),
auth: Some(bcode_model::ProviderAuthContext {
profile: Some("environment".to_owned()),
backend: Some("environment".to_owned()),
diagnostics: vec![bcode_model::ProviderAuthDiagnostic {
severity: "error".to_owned(),
code: "startup_environment_unavailable".to_owned(),
remediation: Some("Reconnect the selected launch environment credential".to_owned()),
message: "Selected launch environment credential is unavailable; no alternate source was selected".to_owned(),
}],
..Default::default()
}),
..Default::default()
},
Ok(None) => {}
}
resolve_provider_request_context_with_registry_loader(
request,
bcode_config::load_runtime_auth_subscriptions,
)
}
pub fn try_resolve_provider_request_context(
request: ProviderRequestContextResolution<'_>,
) -> Result<bcode_model::ProviderRequestContext, bcode_config::ConfigError> {
if let Some(context) =
startup::resolve_remembered_environment(request.config, &request.selection)?
{
return Ok(context);
}
try_resolve_with_registry_loader(request, bcode_config::try_load_runtime_auth_subscriptions)
}
pub fn resolve_explicit_profile_context(
config: &bcode_config::BcodeConfig,
provider_plugin_id: &str,
profile: &str,
) -> Result<bcode_model::ProviderRequestContext, &'static str> {
if provider_plugin_id.trim().is_empty() || profile.trim().is_empty() {
return Err("an explicit provider and auth profile are required");
}
if let Some(declared) = config.auth.profiles.get(profile)
&& (declared.owner_plugin_id.as_deref() != Some(provider_plugin_id)
|| declared
.provider_id
.as_deref()
.is_none_or(|id| id.trim().is_empty()))
{
return Err("selected auth profile ownership cannot be verified");
}
let context = try_resolve_provider_request_context(ProviderRequestContextResolution {
config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: Some(provider_plugin_id.to_owned()),
auth_profile: Some(profile.to_owned()),
..Default::default()
},
})
.map_err(|_| "selected auth profile configuration is unavailable")?;
if context.auth_pool.is_some()
|| !context.auth_candidates.is_empty()
|| context.auth_profile.as_deref() != Some(profile)
|| context
.auth
.as_ref()
.and_then(|auth| auth.profile.as_deref())
!= Some(profile)
{
return Err("selected auth profile could not be resolved");
}
Ok(context)
}
pub fn resolve_owned_profile_context(
config: &bcode_config::BcodeConfig,
provider_id: &str,
owner_plugin_id: &str,
profile: &str,
) -> Result<bcode_model::ProviderRequestContext, &'static str> {
if provider_id.trim().is_empty()
|| owner_plugin_id.trim().is_empty()
|| profile.trim().is_empty()
{
return Err("an explicit provider and auth profile are required");
}
let runtime = if config.auth.profiles.contains_key(profile) || config.active_context.is_some() {
bcode_config::RuntimeAuthSubscriptions::default()
} else {
bcode_config::try_load_runtime_auth_subscriptions()
.map_err(|_| "selected auth profile configuration is unavailable")?
};
resolve_owned_profile_context_with(config, &runtime, provider_id, owner_plugin_id, profile)
}
fn resolve_owned_profile_context_with(
config: &bcode_config::BcodeConfig,
runtime: &bcode_config::RuntimeAuthSubscriptions,
provider_id: &str,
owner_plugin_id: &str,
profile: &str,
) -> Result<bcode_model::ProviderRequestContext, &'static str> {
let resolved =
resolve_auth_provider_profile(config, provider_id, owner_plugin_id, Some(profile), runtime)
.map_err(|_| "selected auth profile ownership cannot be verified")?;
if resolved.profile_name != profile {
return Err("selected auth profile could not be resolved");
}
let auth = resolve_auth_profile(&resolved.profile_name, &resolved.profile);
if auth.auth.credentials.is_empty() {
return Err("selected auth profile credentials are unavailable");
}
Ok(bcode_model::ProviderRequestContext {
auth_profile: Some(resolved.profile_name),
auth: Some(auth.auth),
env: auth.env,
..Default::default()
})
}
pub fn inspect_auth_selection(
request: &ProviderRequestContextResolution<'_>,
) -> Result<(), bcode_config::ConfigError> {
let registry = if request_needs_runtime_registry(request) {
bcode_config::try_load_runtime_auth_subscriptions()?
} else {
bcode_config::RuntimeAuthSubscriptions::default()
};
validate_auth_selection_metadata(request, ®istry)
}
fn validate_auth_selection_metadata(
request: &ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
) -> Result<(), bcode_config::ConfigError> {
bcode_config::contexts::validate_effective(request.config)?;
let empty = bcode_config::RuntimeAuthSubscriptions::default();
let registry = if request.config.active_context.is_some() {
&empty
} else {
registry
};
validate_runtime_account_selection(request, registry)?;
validate_runtime_pool_selection(request, registry)
}
fn request_needs_runtime_registry(request: &ProviderRequestContextResolution<'_>) -> bool {
if request.config.active_context.is_some() {
return false;
}
request.selection.auth_pool.is_some()
|| request
.selection
.auth_profile
.as_ref()
.is_some_and(|name| !request.config.auth.profiles.contains_key(name))
}
fn try_resolve_with_registry_loader(
request: ProviderRequestContextResolution<'_>,
load: impl FnOnce() -> Result<bcode_config::RuntimeAuthSubscriptions, bcode_config::ConfigError>,
) -> Result<bcode_model::ProviderRequestContext, bcode_config::ConfigError> {
let registry = if request_needs_runtime_registry(&request) {
load()?
} else {
bcode_config::RuntimeAuthSubscriptions::default()
};
validate_auth_selection_metadata(&request, ®istry)?;
Ok(resolve_provider_request_context_with_subscriptions(
request, ®istry,
))
}
fn validate_runtime_pool_selection(
request: &ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
) -> Result<(), bcode_config::ConfigError> {
let Some(pool) = request.selection.auth_pool.as_deref() else {
return Ok(());
};
let invalid = || {
bcode_config::ConfigError::Composition {
message: "Selected authentication pool is missing, empty, or contains an unresolved account; inspect pool membership before continuing.".to_owned(),
}
};
if !request.config.auth.pools.contains_key(pool) && !registry.pools.contains_key(pool) {
return Err(invalid());
}
let selected_owner = request.selection.provider_plugin_id.as_deref();
let declared = request.config.auth.pools.get(pool);
let runtime = registry.pools.get(pool);
for owner in [
declared.and_then(|pool| pool.provider_plugin_id.as_deref()),
runtime.and_then(|pool| pool.provider_plugin_id.as_deref()),
runtime.and_then(|pool| pool.owner_plugin_id.as_deref()),
]
.into_iter()
.flatten()
{
if owner.trim().is_empty() || Some(owner) != selected_owner {
return Err(bcode_config::ConfigError::Composition {
message: "Selected authentication pool has conflicting provider ownership; review the selected provider and pool metadata.".to_owned(),
});
}
}
let order = bcode_config::effective_auth_pool_order(
request.config,
registry,
pool,
request.selection.auth_profile.as_deref(),
);
if order.profiles.is_empty() {
return Err(invalid());
}
for name in &order.profiles {
if let Some(profile) = request.config.auth.profiles.get(name) {
validate_declared_request_owner(
profile,
request.selection.provider_plugin_id.as_deref(),
)?;
continue;
}
if runtime_pool_candidate_profile(
request.config,
registry,
pool,
name,
request.selection.provider_plugin_id.as_deref(),
)
.is_none()
{
return Err(invalid());
}
}
Ok(())
}
fn validate_declared_request_owner(
profile: &bcode_config::AuthProfileConfig,
selected_owner: Option<&str>,
) -> Result<(), bcode_config::ConfigError> {
if let Some(owner) = profile.owner_plugin_id.as_deref()
&& (owner.trim().is_empty() || Some(owner) != selected_owner)
{
return Err(bcode_config::ConfigError::Composition {
message: "Selected authentication profile belongs to a different or unselected provider plugin; review the model's account selection.".to_owned(),
});
}
Ok(())
}
fn validate_runtime_account_selection(
request: &ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
) -> Result<(), bcode_config::ConfigError> {
let Some(name) = request.selection.auth_profile.as_deref() else {
return Ok(());
};
if let Some(profile) = request.config.auth.profiles.get(name) {
return validate_declared_request_owner(
profile,
request.selection.provider_plugin_id.as_deref(),
);
}
if selected_runtime_auth_profile(
request.config,
registry,
name,
request.selection.provider_plugin_id.as_deref(),
)
.is_some()
|| request.selection.auth_pool.as_deref().is_some_and(|pool| {
runtime_pool_candidate_profile(
request.config,
registry,
pool,
name,
request.selection.provider_plugin_id.as_deref(),
)
.is_some()
})
{
return Ok(());
}
Err(bcode_config::ConfigError::Composition {
message: "Selected runtime authentication account is missing or its ownership cannot be verified; select an available account or inspect authentication metadata.".to_owned(),
})
}
fn resolve_provider_request_context_with_registry_loader(
request: ProviderRequestContextResolution<'_>,
load: impl FnOnce() -> bcode_config::RuntimeAuthSubscriptions,
) -> bcode_model::ProviderRequestContext {
let registry = if request_needs_runtime_registry(&request) {
load()
} else {
bcode_config::RuntimeAuthSubscriptions::default()
};
resolve_provider_request_context_with_subscriptions(request, ®istry)
}
#[must_use]
pub fn resolve_provider_request_context_with_subscriptions(
request: ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
) -> bcode_model::ProviderRequestContext {
resolve_provider_request_context_with_resolver(request, registry, resolve_auth_profile)
}
#[must_use]
pub fn resolve_provider_request_context_with_resolver(
request: ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
mut resolve: impl FnMut(&str, &bcode_config::AuthProfileConfig) -> ResolvedProviderAuth,
) -> bcode_model::ProviderRequestContext {
let isolated = bcode_config::RuntimeAuthSubscriptions::default();
let registry = if request.config.active_context.is_some() {
&isolated
} else {
registry
};
resolve_request_context_from_registry(request, registry, &mut resolve)
}
fn resolve_request_context_from_registry(
request: ProviderRequestContextResolution<'_>,
registry: &bcode_config::RuntimeAuthSubscriptions,
mut resolve: impl FnMut(&str, &bcode_config::AuthProfileConfig) -> ResolvedProviderAuth,
) -> bcode_model::ProviderRequestContext {
let selected_profile = request.selection.auth_profile.clone();
let mut context = bcode_model::ProviderRequestContext {
model_profile: request.selection.model_profile,
auth_profile: request.selection.auth_profile.clone(),
auth_pool: request.selection.auth_pool.clone(),
auth_pool_routing: selected_auth_pool_routing(
request.config,
request.selection.auth_pool.as_deref(),
),
auth_pool_selection_reason: None,
settings: request.selection.settings,
auth: None,
auth_candidates: Vec::new(),
request: request.selection.request,
env: BTreeMap::new(),
api_surface: None,
};
if let Some(auth_profile_name) = request.selection.auth_profile.as_deref() {
if let Some(auth_profile) = request.config.auth.profiles.get(auth_profile_name) {
let resolved = resolve(auth_profile_name, auth_profile);
context.env = resolved.env;
context.auth = Some(resolved.auth);
} else if let Some(resolved_profile) = selected_runtime_auth_profile(
request.config,
registry,
auth_profile_name,
request.selection.provider_plugin_id.as_deref(),
) {
let resolved = resolve(auth_profile_name, &resolved_profile.profile);
context.env = resolved.env;
context.auth = Some(resolved.auth);
}
}
if let Some(auth_pool_name) = request.selection.auth_pool.as_deref() {
let mut candidates = Vec::new();
let mut seen = BTreeSet::new();
let order = bcode_config::effective_auth_pool_order(
request.config,
registry,
auth_pool_name,
selected_profile.as_deref(),
);
for profile_name in &order.profiles {
if request.config.auth.profiles.contains_key(profile_name) {
push_config_auth_candidate(
request.config,
profile_name,
&mut candidates,
&mut seen,
&mut resolve,
);
continue;
}
if let Some(auth_profile) = runtime_pool_candidate_profile(
request.config,
registry,
auth_pool_name,
profile_name,
request.selection.provider_plugin_id.as_deref(),
) {
if !seen.insert(profile_name.clone()) {
continue;
}
let resolved = resolve(profile_name, &auth_profile);
candidates.push(bcode_model::ProviderAuthCandidate {
profile: Some(profile_name.clone()),
auth: resolved.auth,
env: resolved.env,
});
}
}
context.auth_candidates = candidates;
if let Some(preferred) = order.preferred_profile
&& let Some(candidate) = context
.auth_candidates
.iter()
.find(|candidate| candidate.profile.as_deref() == Some(preferred.as_str()))
{
context.auth_profile = Some(preferred);
context.auth = Some(candidate.auth.clone());
context.env = candidate.env.clone();
}
}
if context.auth.is_none()
&& context.auth_candidates.is_empty()
&& request.selection.auth_profile.is_none()
&& request.selection.auth_pool.is_none()
&& let Some(legacy_auth) = request.config.auth.openai.as_ref()
&& legacy_auth.backend == "sshenv"
{
let profile = legacy_openai_profile(legacy_auth);
let resolved = resolve(&legacy_auth.profile, &profile);
context.auth_profile = Some(legacy_auth.profile.clone());
context.env = resolved.env;
context.auth = Some(resolved.auth);
}
context
}
fn runtime_pool_candidate_profile(
config: &bcode_config::BcodeConfig,
registry: &bcode_config::RuntimeAuthSubscriptions,
pool: &str,
name: &str,
owner: Option<&str>,
) -> Option<bcode_config::AuthProfileConfig> {
if registry.profiles.contains_key(name) {
return selected_runtime_auth_profile(config, registry, name, owner)
.map(|resolved| resolved.profile);
}
let pool = registry.pools.get(pool)?;
let member = pool
.profiles
.iter()
.find(|profile| profile.auth_profile == name)?;
resolve_auth_provider_profile(config, &member.provider, owner?, Some(name), registry)
.ok()
.map(|resolved| resolved.profile)
}
fn selected_runtime_auth_profile(
config: &bcode_config::BcodeConfig,
registry: &bcode_config::RuntimeAuthSubscriptions,
name: &str,
owner: Option<&str>,
) -> Option<ResolvedAuthProfile> {
let profile = registry.profiles.get(name)?;
resolve_auth_provider_profile(config, &profile.provider_id, owner?, Some(name), registry).ok()
}
fn legacy_openai_profile(
auth: &bcode_config::AuthProviderConfig,
) -> bcode_config::AuthProfileConfig {
let (scheme, map) = match auth.mode {
bcode_config::AuthMode::ApiKey => (
"api_key",
BTreeMap::from([(
"api_key".to_owned(),
bcode_config::AuthCredentialMapping {
env: Some("BCODE_OPENAI_API_KEY".to_owned()),
key: None,
},
)]),
),
bcode_config::AuthMode::ChatGpt => (
"chatgpt",
BTreeMap::from([
legacy_openai_credential("access_token", "BCODE_OPENAI_CODEX_ACCESS_TOKEN"),
legacy_openai_credential("refresh_token", "BCODE_OPENAI_CODEX_REFRESH_TOKEN"),
legacy_openai_credential("id_token", "BCODE_OPENAI_CODEX_ID_TOKEN"),
legacy_openai_credential("expires_at", "BCODE_OPENAI_CODEX_EXPIRES_AT"),
legacy_openai_credential("account_id", "BCODE_OPENAI_CODEX_ACCOUNT_ID"),
]),
),
};
let mut settings = BTreeMap::from([
("provider".to_owned(), "openai".to_owned()),
("profile".to_owned(), auth.profile.clone()),
("mode".to_owned(), scheme.to_owned()),
]);
if let Some(vault) = &auth.vault {
settings.insert("vault".to_owned(), vault.display().to_string());
}
bcode_config::AuthProfileConfig {
backend: auth.backend.clone(),
provider_id: Some("openai".to_owned()),
owner_plugin_id: Some("bcode.openai-compatible".to_owned()),
scheme: Some(scheme.to_owned()),
map,
settings,
}
}
fn legacy_openai_credential(
credential_id: &str,
storage_key: &str,
) -> (String, bcode_config::AuthCredentialMapping) {
(
credential_id.to_owned(),
bcode_config::AuthCredentialMapping {
env: Some(storage_key.to_owned()),
key: None,
},
)
}
fn push_config_auth_candidate(
config: &bcode_config::BcodeConfig,
auth_profile_name: &str,
candidates: &mut Vec<bcode_model::ProviderAuthCandidate>,
seen: &mut BTreeSet<String>,
resolve: &mut impl FnMut(&str, &bcode_config::AuthProfileConfig) -> ResolvedProviderAuth,
) {
if !seen.insert(auth_profile_name.to_string()) {
return;
}
if let Some(auth_profile) = config.auth.profiles.get(auth_profile_name) {
let resolved = resolve(auth_profile_name, auth_profile);
candidates.push(bcode_model::ProviderAuthCandidate {
profile: Some(auth_profile_name.to_string()),
auth: resolved.auth,
env: resolved.env,
});
}
}
fn runtime_subscription_auth_profile_config(
profile: &bcode_config::RuntimeAuthSubscriptionProfile,
) -> bcode_config::AuthProfileConfig {
bcode_config::AuthProfileConfig {
backend: "sshenv".to_string(),
provider_id: Some(profile.provider.clone()),
owner_plugin_id: profile.owner_plugin_id.clone(),
scheme: Some(profile.scheme.clone()),
map: profile.map.clone(),
settings: {
let mut settings = BTreeMap::from([
("provider".to_string(), profile.provider.clone()),
("profile".to_string(), profile.storage_profile.clone()),
("vault".to_string(), profile.vault.display().to_string()),
("mode".to_string(), profile.scheme.clone()),
]);
if let Some(device_seal) = &profile.device_seal {
settings.insert("device_seal".to_owned(), device_seal.clone());
}
settings
},
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedAuthProfile {
pub profile_name: String,
pub provider_id: String,
pub owner_plugin_id: String,
pub profile: bcode_config::AuthProfileConfig,
pub source: AuthProfileSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthProfileSource {
Declarative,
Runtime,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthProviderProfileLookup {
Configured(ResolvedAuthProfile),
Unconfigured { profile_name: String },
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum AuthProfileResolutionError {
#[error("auth provider and plugin IDs must not be empty")]
InvalidOwner,
#[error(
"auth profile '{profile}' has conflicting pool registrations; inspect authentication metadata before continuing"
)]
AmbiguousProfile { profile: String },
#[error("auth profile '{profile}' is not configured for provider '{provider_id}'")]
MissingProfile {
provider_id: String,
profile: String,
},
#[error(
"auth profile '{profile}' cannot prove registered provider ownership: missing {missing}"
)]
OwnershipUnverifiable { profile: String, missing: String },
#[error("auth profile '{profile}' belongs to provider '{actual}', not '{expected}'")]
ProviderMismatch {
profile: String,
expected: String,
actual: String,
},
#[error("auth profile '{profile}' belongs to plugin '{actual}', not '{expected}'")]
OwnerMismatch {
profile: String,
expected: String,
actual: String,
},
}
pub fn lookup_auth_provider_profile(
config: &bcode_config::BcodeConfig,
provider_id: &str,
owner_plugin_id: &str,
explicit_profile: Option<&str>,
runtime: &bcode_config::RuntimeAuthSubscriptions,
) -> Result<AuthProviderProfileLookup, AuthProfileResolutionError> {
match resolve_auth_provider_profile(
config,
provider_id,
owner_plugin_id,
explicit_profile,
runtime,
) {
Ok(resolved) => Ok(AuthProviderProfileLookup::Configured(resolved)),
Err(AuthProfileResolutionError::MissingProfile { profile, .. })
if explicit_profile.is_none()
&& !config.auth.bindings.contains_key(provider_id)
&& !runtime.bindings.contains_key(provider_id)
&& !config.auth.profiles.contains_key(&profile)
&& !runtime.profiles.contains_key(&profile) =>
{
Ok(AuthProviderProfileLookup::Unconfigured {
profile_name: profile,
})
}
Err(error) => Err(error),
}
}
#[must_use]
pub fn discover_provider_profiles(
config: &bcode_config::BcodeConfig,
provider_id: &str,
owner_plugin_id: &str,
runtime: &bcode_config::RuntimeAuthSubscriptions,
) -> BTreeMap<String, Result<ResolvedAuthProfile, AuthProfileResolutionError>> {
let mut names = config
.auth
.profiles
.keys()
.cloned()
.collect::<std::collections::BTreeSet<_>>();
if config.active_context.is_none() {
names.extend(runtime.profiles.keys().cloned());
names.extend(runtime.pools.values().flat_map(|pool| {
pool.profiles
.iter()
.map(|member| member.auth_profile.clone())
}));
}
names
.into_iter()
.map(|name| {
let result = resolve_auth_provider_profile(
config,
provider_id,
owner_plugin_id,
Some(&name),
runtime,
);
(name, result)
})
.collect()
}
pub fn resolve_auth_provider_profile(
config: &bcode_config::BcodeConfig,
provider_id: &str,
owner_plugin_id: &str,
explicit_profile: Option<&str>,
runtime: &bcode_config::RuntimeAuthSubscriptions,
) -> Result<ResolvedAuthProfile, AuthProfileResolutionError> {
if provider_id.trim().is_empty() || owner_plugin_id.trim().is_empty() {
return Err(AuthProfileResolutionError::InvalidOwner);
}
let scoped_name = context_profile_name(config, provider_id, explicit_profile)?;
let explicit_profile = scoped_name.as_deref().or(explicit_profile);
let declarative_binding = config
.auth
.bindings
.get(provider_id)
.and_then(|binding| binding.profile.as_deref());
let runtime_binding = config
.active_context
.is_none()
.then(|| runtime.bindings.get(provider_id))
.flatten();
let profile_name = explicit_profile
.or(declarative_binding)
.or_else(|| {
config
.auth
.profiles
.contains_key(provider_id)
.then_some(provider_id)
})
.or_else(|| runtime_binding.map(|binding| binding.profile.as_str()))
.unwrap_or(provider_id);
if let Some(profile) = config.auth.profiles.get(profile_name) {
validate_auth_profile_ownership(profile_name, profile, provider_id, owner_plugin_id)?;
return Ok(ResolvedAuthProfile {
profile_name: profile_name.to_string(),
provider_id: provider_id.to_string(),
owner_plugin_id: owner_plugin_id.to_string(),
profile: profile.clone(),
source: AuthProfileSource::Declarative,
});
}
reject_unconfigured_context_account(config, provider_id, profile_name)?;
let Some(runtime_profile) = runtime.profiles.get(profile_name) else {
return resolve_runtime_pool_member_profile(
runtime,
profile_name,
provider_id,
owner_plugin_id,
runtime_binding,
);
};
if let Some(binding) = runtime_binding
&& binding.profile == profile_name
&& binding.owner_plugin_id != owner_plugin_id
{
return Err(AuthProfileResolutionError::OwnerMismatch {
profile: profile_name.to_string(),
expected: owner_plugin_id.to_string(),
actual: binding.owner_plugin_id.clone(),
});
}
if runtime_profile.provider_id != provider_id {
return Err(AuthProfileResolutionError::ProviderMismatch {
profile: profile_name.to_string(),
expected: provider_id.to_string(),
actual: runtime_profile.provider_id.clone(),
});
}
if runtime_profile.owner_plugin_id != owner_plugin_id {
return Err(AuthProfileResolutionError::OwnerMismatch {
profile: profile_name.to_string(),
expected: owner_plugin_id.to_string(),
actual: runtime_profile.owner_plugin_id.clone(),
});
}
Ok(ResolvedAuthProfile {
profile_name: profile_name.to_string(),
provider_id: provider_id.to_string(),
owner_plugin_id: owner_plugin_id.to_string(),
profile: bcode_config::AuthProfileConfig {
backend: runtime_profile.backend.clone(),
provider_id: Some(provider_id.to_string()),
owner_plugin_id: Some(owner_plugin_id.to_string()),
scheme: Some(runtime_profile.scheme.clone()),
map: runtime_profile.map.clone(),
settings: {
let mut settings = BTreeMap::from([
(
"profile".to_string(),
runtime_profile.storage_profile.clone(),
),
(
"vault".to_string(),
runtime_profile.vault.display().to_string(),
),
]);
if let Some(device_seal) = &runtime_profile.device_seal {
settings.insert("device_seal".to_owned(), device_seal.clone());
}
settings
},
},
source: AuthProfileSource::Runtime,
})
}
fn reject_unconfigured_context_account(
config: &bcode_config::BcodeConfig,
provider: &str,
profile: &str,
) -> Result<(), AuthProfileResolutionError> {
if config.active_context.is_some() {
return Err(AuthProfileResolutionError::MissingProfile {
provider_id: provider.to_owned(),
profile: profile.to_owned(),
});
}
Ok(())
}
fn context_profile_name(
config: &bcode_config::BcodeConfig,
provider_id: &str,
explicit: Option<&str>,
) -> Result<Option<String>, AuthProfileResolutionError> {
let Some(context) = config.active_context.as_deref() else {
return Ok(None);
};
let requested = explicit
.or_else(|| {
config
.auth
.bindings
.get(provider_id)
.and_then(|binding| binding.profile.as_deref())
})
.unwrap_or(provider_id);
let namespace = format!("ctx-{}-{context}-", context.len());
if requested.starts_with(&namespace) {
return Ok(Some(requested.to_owned()));
}
bcode_config::contexts::qualify(context, requested)
.map(Some)
.map_err(|_| AuthProfileResolutionError::InvalidOwner)
}
fn resolve_runtime_pool_member_profile(
runtime: &bcode_config::RuntimeAuthSubscriptions,
profile_name: &str,
provider_id: &str,
owner_plugin_id: &str,
runtime_binding: Option<&bcode_config::RuntimeAuthBinding>,
) -> Result<ResolvedAuthProfile, AuthProfileResolutionError> {
let mut matches = runtime.pools.values().flat_map(|pool| {
pool.profiles
.iter()
.filter(move |member| member.auth_profile == profile_name)
.map(move |member| (pool, member))
});
let Some((pool, member)) = matches.next() else {
return Err(AuthProfileResolutionError::MissingProfile {
provider_id: provider_id.to_string(),
profile: profile_name.to_string(),
});
};
if let Some(binding) = runtime_binding
&& binding.profile == profile_name
&& binding.owner_plugin_id != owner_plugin_id
{
return Err(AuthProfileResolutionError::OwnerMismatch {
profile: profile_name.to_string(),
expected: owner_plugin_id.to_string(),
actual: binding.owner_plugin_id.clone(),
});
}
let profile = owned_pool_member_config(pool, member);
for (other_pool, other_member) in matches {
if owned_pool_member_config(other_pool, other_member) != profile {
return Err(AuthProfileResolutionError::AmbiguousProfile {
profile: profile_name.to_owned(),
});
}
}
validate_auth_profile_ownership(profile_name, &profile, provider_id, owner_plugin_id)?;
Ok(ResolvedAuthProfile {
profile_name: profile_name.to_string(),
provider_id: provider_id.to_string(),
owner_plugin_id: owner_plugin_id.to_string(),
profile,
source: AuthProfileSource::Runtime,
})
}
fn owned_pool_member_config(
pool: &bcode_config::RuntimeAuthSubscriptionPool,
member: &bcode_config::RuntimeAuthSubscriptionProfile,
) -> bcode_config::AuthProfileConfig {
let mut profile = runtime_subscription_auth_profile_config(member);
if profile.owner_plugin_id.is_none() {
profile.owner_plugin_id = pool
.owner_plugin_id
.clone()
.or_else(|| pool.provider_plugin_id.clone());
}
profile
}
fn validate_auth_profile_ownership(
profile_name: &str,
profile: &bcode_config::AuthProfileConfig,
provider_id: &str,
owner_plugin_id: &str,
) -> Result<(), AuthProfileResolutionError> {
let Some(actual_provider_id) = &profile.provider_id else {
return Err(AuthProfileResolutionError::OwnershipUnverifiable {
profile: profile_name.to_string(),
missing: "provider_id".to_owned(),
});
};
if actual_provider_id != provider_id {
return Err(AuthProfileResolutionError::ProviderMismatch {
profile: profile_name.to_string(),
expected: provider_id.to_string(),
actual: actual_provider_id.clone(),
});
}
let Some(actual_owner_plugin_id) = &profile.owner_plugin_id else {
return Err(AuthProfileResolutionError::OwnershipUnverifiable {
profile: profile_name.to_string(),
missing: "owner_plugin_id".to_owned(),
});
};
if actual_owner_plugin_id != owner_plugin_id {
return Err(AuthProfileResolutionError::OwnerMismatch {
profile: profile_name.to_string(),
expected: owner_plugin_id.to_string(),
actual: actual_owner_plugin_id.clone(),
});
}
Ok(())
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedProviderAuth {
pub auth: bcode_model::ProviderAuthContext,
pub env: BTreeMap<String, String>,
}
pub fn resolve_declared_environment_context(
contribution: &bcode_provider_auth_models::AuthProviderContribution,
) -> Result<bcode_model::ProviderRequestContext, &'static str> {
resolve_declared_environment_context_with(contribution, |name| std::env::var(name).ok())
}
fn resolve_declared_environment_context_with(
contribution: &bcode_provider_auth_models::AuthProviderContribution,
mut read_env: impl FnMut(&str) -> Option<String>,
) -> Result<bcode_model::ProviderRequestContext, &'static str> {
use bcode_provider_auth_models::AuthMethodContribution;
contribution
.validate()
.map_err(|_| "invalid provider auth declaration")?;
let mut selected = None;
for method in &contribution.methods {
if let AuthMethodContribution::SecretFields {
fields, method_id, ..
} = method
&& fields.iter().any(|field| !field.invocation_env.is_empty())
{
if selected.is_some() {
return Err("provider environment method is unavailable or ambiguous");
}
selected = Some((method_id, fields));
}
}
let (method_id, fields) =
selected.ok_or("provider environment method is unavailable or ambiguous")?;
let mut credentials = BTreeMap::new();
for field in fields {
if field.invocation_env.is_empty() {
if !field.optional {
return Err("provider environment credentials are incomplete");
}
continue;
}
let mut selected: Option<(String, String)> = None;
for name in &field.invocation_env {
if let Some(value) = read_env(name) {
if value.is_empty() {
return Err("provider environment credential is invalid");
}
field
.validation
.validate_secret(&value)
.map_err(|_| "provider environment credential is invalid")?;
if selected
.as_ref()
.is_some_and(|(_, previous)| previous != &value)
{
return Err("provider environment credentials conflict");
}
if selected.is_none() {
selected = Some((name.clone(), value));
}
}
}
match selected {
Some((source, value)) => {
credentials.insert(
field.credential_id.clone(),
bcode_model::ProviderAuthCredential {
value,
source: Some(source),
},
);
}
None if !field.optional => {
return Err("provider environment credentials are unavailable");
}
None => {}
}
}
let auth = bcode_model::ProviderAuthContext {
profile: Some("environment".into()),
backend: Some("environment".into()),
scheme: Some(method_id.clone()),
credentials,
..Default::default()
};
Ok(bcode_model::ProviderRequestContext {
auth_profile: Some("environment".into()),
auth: Some(auth),
..Default::default()
})
}
#[must_use]
pub fn resolve_auth_profile(
auth_profile_name: &str,
auth_profile: &bcode_config::AuthProfileConfig,
) -> ResolvedProviderAuth {
let mut env = BTreeMap::new();
let mut storage_profile = auth_profile_name.to_string();
let mut storage_vault = None;
let mut diagnostics = Vec::new();
match auth_profile.backend.as_str() {
"sshenv" => {
let vault = auth_profile
.settings
.get("vault")
.map_or_else(bcode_config::default_auth_vault_path, PathBuf::from);
let profile = auth_profile
.settings
.get("profile")
.map_or(auth_profile_name, String::as_str);
storage_profile = profile.to_string();
storage_vault = Some(vault.display().to_string());
let options = security::device_seal_options_for_auth_profile(auth_profile);
let recipient_key = auth_profile
.settings
.get("recipient_key")
.map(String::as_str)
.map_or_else(
|| security::ensure_vault_recipient_key(&vault).ok(),
|key| Some(key.to_string()),
);
let report = security::reconcile_auth_vault_security_report_with_options(
&vault,
profile,
options,
recipient_key.as_deref(),
);
diagnostics.extend(report.diagnostics);
match security::read_auth_vault_profile(&vault, profile) {
Ok(Some(profile_env)) => {
for (key, value) in profile_env {
env.entry(key).or_insert(value);
}
}
Ok(None) => {}
Err(error) => diagnostics.push(security::AuthSecurityDiagnostic {
severity: security::AuthSecurityDiagnosticSeverity::Warning,
code: "auth_vault_profile_unavailable".to_string(),
message: error,
remediation: Some(
"Run `bcode login` to recreate this profile using the Bcode-managed per-vault key."
.to_string(),
),
}),
}
merge_metadata_env(auth_profile, profile, &vault, &mut env);
merge_mapped_process_env(auth_profile, &mut env);
merge_settings_env(auth_profile, &mut env);
}
"env" => {
merge_mapped_process_env(auth_profile, &mut env);
merge_settings_env(auth_profile, &mut env);
}
"aws" | "aws_default_chain" => merge_settings_env(auth_profile, &mut env),
_ => {}
}
let auth = provider_auth_context(
auth_profile_name,
auth_profile,
&storage_profile,
storage_vault.as_deref(),
&env,
diagnostics,
);
ResolvedProviderAuth { auth, env }
}
fn merge_metadata_env(
auth_profile: &bcode_config::AuthProfileConfig,
profile: &str,
vault: &std::path::Path,
env: &mut BTreeMap<String, String>,
) {
match auth_profile.settings.get("provider").map(String::as_str) {
Some("openai") => {
env.entry("BCODE_OPENAI_AUTH_PROFILE".to_string())
.or_insert_with(|| profile.to_string());
env.entry("BCODE_OPENAI_AUTH_VAULT".to_string())
.or_insert_with(|| vault.display().to_string());
}
Some("xai" | "grok") => {
env.entry("BCODE_XAI_AUTH_PROFILE".to_string())
.or_insert_with(|| profile.to_string());
env.entry("BCODE_XAI_AUTH_VAULT".to_string())
.or_insert_with(|| vault.display().to_string());
}
_ => {}
}
}
fn merge_mapped_process_env(
auth_profile: &bcode_config::AuthProfileConfig,
env: &mut BTreeMap<String, String>,
) {
for source_key in auth_credential_source_keys(auth_profile).values() {
if let Ok(value) = std::env::var(source_key)
&& !value.trim().is_empty()
{
env.entry(source_key.clone()).or_insert(value);
}
}
}
fn merge_settings_env(
auth_profile: &bcode_config::AuthProfileConfig,
env: &mut BTreeMap<String, String>,
) {
for (key, value) in &auth_profile.settings {
if let Some(env_key) = key.strip_prefix("env.") {
env.entry(env_key.to_string())
.or_insert_with(|| value.clone());
}
}
match auth_profile.settings.get("provider").map(String::as_str) {
Some("openai") => {
copy_setting_to_env(auth_profile, env, "mode", "BCODE_OPENAI_AUTH_MODE");
copy_setting_to_env(auth_profile, env, "base_url", "BCODE_OPENAI_BASE_URL");
}
Some("xai" | "grok") => {
copy_setting_to_env(auth_profile, env, "base_url", "BCODE_XAI_BASE_URL");
}
Some("aws" | "bedrock") => {
let aws_profile_key = if auth_profile.backend == "sshenv" {
"aws_profile"
} else {
"profile"
};
copy_setting_to_env(auth_profile, env, aws_profile_key, "AWS_PROFILE");
copy_setting_to_env(
auth_profile,
env,
aws_profile_key,
"BCODE_BEDROCK_AWS_PROFILE",
);
copy_setting_to_env(auth_profile, env, "region", "AWS_REGION");
copy_setting_to_env(auth_profile, env, "region", "BCODE_BEDROCK_REGION");
copy_setting_to_env(
auth_profile,
env,
"endpoint_url",
"BCODE_BEDROCK_ENDPOINT_URL",
);
}
_ => {}
}
}
fn copy_setting_to_env(
auth_profile: &bcode_config::AuthProfileConfig,
env: &mut BTreeMap<String, String>,
setting_key: &str,
env_key: &str,
) {
if let Some(value) = auth_profile.settings.get(setting_key) {
env.entry(env_key.to_string())
.or_insert_with(|| value.clone());
}
}
fn provider_auth_context(
auth_profile_name: &str,
auth_profile: &bcode_config::AuthProfileConfig,
storage_profile: &str,
storage_vault: Option<&str>,
env: &BTreeMap<String, String>,
diagnostics: Vec<security::AuthSecurityDiagnostic>,
) -> bcode_model::ProviderAuthContext {
let source_keys = auth_credential_source_keys(auth_profile);
let credentials = source_keys
.iter()
.filter_map(|(credential, source_key)| {
env.get(source_key)
.filter(|value| !value.is_empty())
.map(|value| {
(
credential.clone(),
bcode_model::ProviderAuthCredential {
value: value.clone(),
source: Some(source_key.clone()),
},
)
})
})
.collect::<BTreeMap<_, _>>();
let storage = source_keys
.into_iter()
.map(|(credential, source_key)| {
(
credential,
bcode_model::ProviderAuthStorageRef {
backend: auth_profile.backend.clone(),
profile: storage_profile.to_string(),
key: source_key,
vault: storage_vault.map(ToString::to_string),
},
)
})
.collect();
bcode_model::ProviderAuthContext {
profile: Some(auth_profile_name.to_string()),
backend: Some(auth_profile.backend.clone()),
scheme: auth_profile
.scheme
.clone()
.or_else(|| auth_profile.settings.get("mode").cloned())
.or_else(|| (!credentials.is_empty()).then(|| "api_key".to_string())),
credentials,
attributes: auth_profile.settings.clone(),
storage,
diagnostics: diagnostics
.into_iter()
.map(|diagnostic| bcode_model::ProviderAuthDiagnostic {
severity: diagnostic.severity.as_str().to_string(),
code: diagnostic.code,
message: diagnostic.message,
remediation: diagnostic.remediation,
})
.collect(),
}
}
fn auth_credential_source_keys(
auth_profile: &bcode_config::AuthProfileConfig,
) -> BTreeMap<String, String> {
let mut source_keys = auth_profile
.map
.iter()
.filter_map(|(credential, mapping)| {
mapping
.env
.as_ref()
.or(mapping.key.as_ref())
.filter(|key| !key.trim().is_empty())
.map(|key| (credential.clone(), key.clone()))
})
.collect::<BTreeMap<_, _>>();
if let Some(api_key_env) = auth_profile
.settings
.get("api_key_env")
.filter(|value| !value.trim().is_empty())
{
source_keys
.entry("api_key".to_string())
.or_insert_with(|| api_key_env.clone());
}
if matches!(
auth_profile.settings.get("provider").map(String::as_str),
Some("aws" | "bedrock")
) {
for (credential, key) in [
("access_key_id", "AWS_ACCESS_KEY_ID"),
("secret_access_key", "AWS_SECRET_ACCESS_KEY"),
("session_token", "AWS_SESSION_TOKEN"),
("bearer_token", "AWS_BEARER_TOKEN_BEDROCK"),
] {
source_keys
.entry(credential.to_string())
.or_insert_with(|| key.to_string());
}
}
if auth_profile
.settings
.get("mode")
.is_some_and(|mode| mode == "chatgpt")
{
for (credential, key) in [
("access_token", "BCODE_OPENAI_CODEX_ACCESS_TOKEN"),
("refresh_token", "BCODE_OPENAI_CODEX_REFRESH_TOKEN"),
("id_token", "BCODE_OPENAI_CODEX_ID_TOKEN"),
("expires_at", "BCODE_OPENAI_CODEX_EXPIRES_AT"),
("account_id", "BCODE_OPENAI_CODEX_ACCOUNT_ID"),
] {
source_keys
.entry(credential.to_string())
.or_insert_with(|| key.to_string());
}
}
source_keys
}
#[must_use]
pub fn selected_auth_pool_routing(
config: &bcode_config::BcodeConfig,
auth_pool: Option<&str>,
) -> bcode_model::ProviderAuthPoolRouting {
let Some(auth_pool) = auth_pool else {
return bcode_model::ProviderAuthPoolRouting::default();
};
let Some(pool) = config.auth.pools.get(auth_pool) else {
return bcode_model::ProviderAuthPoolRouting::default();
};
let required_windows = pool.priming.required_windows.clone();
bcode_model::ProviderAuthPoolRouting {
strategy: Some(match pool.strategy {
bcode_config::AuthPoolStrategy::Failover => "failover".to_string(),
bcode_config::AuthPoolStrategy::RoundRobin => "round_robin".to_string(),
}),
priming_enabled: pool.priming.enabled,
priming_include_primary: pool.priming.include_primary,
priming_reprime_after: pool.priming.reprime_after.clone(),
priming_provider_windows: pool.priming.provider_windows,
priming_fallback_reprime_after: pool.priming.fallback_reprime_after.clone(),
priming_required_windows: required_windows,
}
}
#[cfg(test)]
mod tests {
#[test]
fn environment_invocation_requires_declaration_and_unambiguous_keys() {
use bcode_provider_auth_models::{
AUTH_PROVIDER_CONTRIBUTION_SCHEMA_VERSION, AuthMethodContribution,
AuthProviderContribution, AuthSecretField, AuthSecretValidation,
};
let contribution = AuthProviderContribution {
schema_version: AUTH_PROVIDER_CONTRIBUTION_SCHEMA_VERSION,
provider_id: "test-provider".into(),
display_name: "Test provider".into(),
methods: vec![AuthMethodContribution::SecretFields {
method_id: "api_key".into(),
display_name: "API key".into(),
fields: vec![AuthSecretField {
credential_id: "api_key".into(),
storage_key: "BCODE_TEST_KEY".into(),
prompt: "API key".into(),
optional: false,
validation: AuthSecretValidation {
min_bytes: Some(1),
max_bytes: Some(64),
required_prefix: None,
},
discovery_sources: Vec::new(),
invocation_env: vec!["BCODE_TEST_KEY".into(), "TEST_KEY".into()],
}],
supports_verification: false,
supports_revocation: false,
}],
};
let context = super::resolve_declared_environment_context_with(&contribution, |name| {
(name == "TEST_KEY").then(|| "secret".into())
})
.unwrap();
assert_eq!(
context.auth.unwrap().credentials["api_key"]
.source
.as_deref(),
Some("TEST_KEY")
);
assert!(super::resolve_declared_environment_context_with(&contribution, |_| None).is_err());
assert_eq!(
super::resolve_declared_environment_context_with(
&contribution,
|_| Some(String::new())
)
.unwrap_err(),
"provider environment credential is invalid"
);
assert_eq!(
super::resolve_declared_environment_context_with(&contribution, |name| Some(
name.into()
))
.unwrap_err(),
"provider environment credentials conflict"
);
let mut undeclared = contribution.clone();
if let AuthMethodContribution::SecretFields { fields, .. } = &mut undeclared.methods[0] {
fields[0].invocation_env.clear();
}
assert!(
super::resolve_declared_environment_context_with(&undeclared, |_| Some(
"secret".into()
))
.is_err()
);
let mut ambiguous = contribution;
ambiguous.methods.push(ambiguous.methods[0].clone());
assert!(
super::resolve_declared_environment_context_with(&ambiguous, |_| Some("secret".into()))
.is_err()
);
let mut invalid = ambiguous;
if let AuthMethodContribution::SecretFields { fields, .. } = &mut invalid.methods[0] {
fields[0].invocation_env = vec!["TEST_KEY".into(), "TEST_KEY".into()];
}
assert!(invalid.validate().is_err());
}
#[test]
fn explicit_profile_rejects_blank_selection_before_materialization() {
let config = bcode_config::BcodeConfig::default();
for (provider, profile) in [("", "profile"), ("provider", ""), ("provider", " \t")] {
assert_eq!(
super::resolve_explicit_profile_context(&config, provider, profile).unwrap_err(),
"an explicit provider and auth profile are required"
);
}
}
#[test]
fn explicit_profile_rejects_unverifiable_ownership_before_materialization() {
for (owner, provider) in [
(None, Some("provider")),
(Some(""), Some("provider")),
(Some("other-plugin"), Some("provider")),
(Some("plugin"), None),
(Some("plugin"), Some(" \t")),
] {
let mut config = bcode_config::BcodeConfig::default();
config.auth.profiles.insert(
"selected".into(),
bcode_config::AuthProfileConfig {
backend: "sshenv".into(),
owner_plugin_id: owner.map(str::to_owned),
provider_id: provider.map(str::to_owned),
..Default::default()
},
);
assert_eq!(
super::resolve_explicit_profile_context(&config, "plugin", "selected").unwrap_err(),
"selected auth profile ownership cannot be verified"
);
}
}
#[test]
fn owned_judgement_profile_distinguishes_provider_id_from_plugin_id() {
let mut config = bcode_config::BcodeConfig::default();
config.auth.profiles.insert(
"jev".into(),
bcode_config::AuthProfileConfig {
backend: "aws".into(),
owner_plugin_id: Some("bcode.jev".into()),
provider_id: Some("jev".into()),
scheme: Some("api_key".into()),
map: BTreeMap::from([(
"api_key".into(),
bcode_config::AuthCredentialMapping {
env: Some("BCODE_JEV_API_KEY".into()),
key: None,
},
)]),
settings: BTreeMap::from([("env.BCODE_JEV_API_KEY".into(), "test-only".into())]),
},
);
let runtime = bcode_config::RuntimeAuthSubscriptions::default();
let context =
super::resolve_owned_profile_context_with(&config, &runtime, "jev", "bcode.jev", "jev")
.unwrap();
assert_eq!(context.auth_profile.as_deref(), Some("jev"));
assert_eq!(
context.auth.unwrap().credentials["api_key"].value,
"test-only"
);
for (provider, owner) in [("other", "bcode.jev"), ("jev", "bcode.other")] {
assert!(
super::resolve_owned_profile_context_with(
&config, &runtime, provider, owner, "jev",
)
.is_err()
);
}
}
#[test]
fn explicit_preference_updates_only_matching_owned_state() {
let mut config = bcode_config::BcodeConfig::default();
config.auth.pools.insert(
"pool".into(),
bcode_config::AuthPoolConfig {
profiles: vec!["a".into(), "b".into()],
..bcode_config::AuthPoolConfig::default()
},
);
let mut subscriptions = bcode_config::RuntimeAuthSubscriptions::default();
let mut routing = super::auth_pool_state::AuthPoolState::default();
routing.pools.insert(
"pool".into(),
super::auth_pool_state::AuthPoolRoutingState {
last_selected_profile: Some("b".into()),
},
);
let untouched = routing.clone();
assert!(
super::update_auth_pool_preference(
&config,
&mut subscriptions,
&mut routing,
"pool",
Some("unknown")
)
.is_err()
);
assert_eq!(routing, untouched);
assert!(subscriptions.pools.is_empty());
super::update_auth_pool_preference(
&config,
&mut subscriptions,
&mut routing,
"pool",
Some("a"),
)
.expect("valid preference");
assert_eq!(
subscriptions.pools["pool"].preferred_profile.as_deref(),
Some("a")
);
assert_eq!(routing.pools["pool"].last_selected_profile, None);
assert_eq!(
untouched.pools["pool"].last_selected_profile.as_deref(),
Some("b")
);
}
use super::*;
fn legacy_openai_method(
mode: &bcode_config::AuthMode,
) -> bcode_provider_auth_models::AuthMethodContribution {
match mode {
bcode_config::AuthMode::ApiKey => {
bcode_provider_auth_models::AuthMethodContribution::SecretFields {
method_id: "api_key".to_owned(),
display_name: "API key".to_owned(),
fields: vec![bcode_provider_auth_models::AuthSecretField {
invocation_env: Vec::new(),
discovery_sources: Vec::new(),
credential_id: "api_key".to_owned(),
storage_key: "BCODE_OPENAI_API_KEY".to_owned(),
prompt: "OpenAI API key".to_owned(),
optional: false,
validation: bcode_provider_auth_models::AuthSecretValidation::default(),
}],
supports_verification: false,
supports_revocation: false,
}
}
bcode_config::AuthMode::ChatGpt => {
bcode_provider_auth_models::AuthMethodContribution::Interactive {
method_id: "chatgpt".to_owned(),
display_name: "ChatGPT".to_owned(),
operation: "flow".to_owned(),
credentials: [
("access_token", "BCODE_OPENAI_CODEX_ACCESS_TOKEN"),
("refresh_token", "BCODE_OPENAI_CODEX_REFRESH_TOKEN"),
("id_token", "BCODE_OPENAI_CODEX_ID_TOKEN"),
("expires_at", "BCODE_OPENAI_CODEX_EXPIRES_AT"),
("account_id", "BCODE_OPENAI_CODEX_ACCOUNT_ID"),
]
.into_iter()
.map(|(credential_id, storage_key)| {
bcode_provider_auth_models::AuthCredentialStorage {
credential_id: credential_id.to_owned(),
storage_key: storage_key.to_owned(),
}
})
.collect(),
supports_revocation: false,
}
}
}
}
fn legacy_openai_resolved(auth: &bcode_config::AuthProviderConfig) -> ResolvedAuthProfile {
let mut profile = legacy_openai_profile(auth);
profile
.settings
.insert("device_seal".to_owned(), "off".to_owned());
ResolvedAuthProfile {
profile_name: auth.profile.clone(),
provider_id: "openai".to_owned(),
owner_plugin_id: "bcode.openai-compatible".to_owned(),
profile,
source: AuthProfileSource::Declarative,
}
}
#[test]
#[cfg(not(feature = "custody-simulation"))]
fn legacy_openai_api_key_round_trips_through_host_custody() {
let temp = tempfile::tempdir().expect("tempdir");
let auth = bcode_config::AuthProviderConfig {
backend: "sshenv".to_owned(),
mode: bcode_config::AuthMode::ApiKey,
profile: "legacy-openai-api".to_owned(),
vault: Some(temp.path().join("vault")),
};
let resolved = legacy_openai_resolved(&auth);
let method = legacy_openai_method(&auth.mode);
lifecycle::AuthVaultLifecycle::new(&resolved, "openai", "bcode.openai-compatible", &method)
.expect("owned legacy lifecycle")
.upsert(BTreeMap::from([(
"api_key".to_owned(),
"legacy-api-key".to_owned(),
)]))
.expect("store legacy API key");
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
openai: Some(auth),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let context = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection::default(),
});
let semantic = context.auth.expect("host semantic auth");
assert_eq!(semantic.profile.as_deref(), Some("legacy-openai-api"));
assert_eq!(semantic.scheme.as_deref(), Some("api_key"));
assert_eq!(
semantic
.credentials
.get("api_key")
.map(|credential| credential.value.as_str()),
Some("legacy-api-key")
);
}
#[test]
#[cfg(not(feature = "custody-simulation"))]
fn legacy_openai_chatgpt_round_trips_through_host_custody() {
let temp = tempfile::tempdir().expect("tempdir");
let auth = bcode_config::AuthProviderConfig {
backend: "sshenv".to_owned(),
mode: bcode_config::AuthMode::ChatGpt,
profile: "legacy-openai-chatgpt".to_owned(),
vault: Some(temp.path().join("vault")),
};
let resolved = legacy_openai_resolved(&auth);
let method = legacy_openai_method(&auth.mode);
lifecycle::AuthVaultLifecycle::new(&resolved, "openai", "bcode.openai-compatible", &method)
.expect("owned legacy lifecycle")
.replace_owned(BTreeMap::from([
("access_token".to_owned(), "legacy-access".to_owned()),
("refresh_token".to_owned(), "legacy-refresh".to_owned()),
("expires_at".to_owned(), "12345".to_owned()),
("account_id".to_owned(), "account-1".to_owned()),
]))
.expect("store legacy ChatGPT credentials");
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
openai: Some(auth),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let context = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection::default(),
});
let semantic = context.auth.expect("host semantic auth");
assert_eq!(semantic.profile.as_deref(), Some("legacy-openai-chatgpt"));
assert_eq!(semantic.scheme.as_deref(), Some("chatgpt"));
assert_eq!(
semantic
.credentials
.get("access_token")
.map(|credential| credential.value.as_str()),
Some("legacy-access")
);
assert_eq!(
semantic
.credentials
.get("refresh_token")
.map(|credential| credential.value.as_str()),
Some("legacy-refresh")
);
assert_eq!(
semantic
.credentials
.get("account_id")
.map(|credential| credential.value.as_str()),
Some("account-1")
);
}
#[test]
fn legacy_openai_ownership_fails_before_vault_access() {
let temp = tempfile::tempdir().expect("tempdir");
let vault = temp.path().join("must-not-exist");
let auth = bcode_config::AuthProviderConfig {
backend: "sshenv".to_owned(),
mode: bcode_config::AuthMode::ApiKey,
profile: "legacy-openai".to_owned(),
vault: Some(vault.clone()),
};
let resolved = legacy_openai_resolved(&auth);
let method = legacy_openai_method(&auth.mode);
assert!(matches!(
lifecycle::AuthVaultLifecycle::new(&resolved, "openai", "bcode.other", &method,),
Err(lifecycle::AuthVaultLifecycleError::Ownership(
AuthProfileResolutionError::OwnerMismatch { .. }
))
));
assert!(!vault.exists());
}
#[test]
fn legacy_openai_profile_is_host_materialized_only_without_explicit_selection() {
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
openai: Some(bcode_config::AuthProviderConfig {
backend: "sshenv".to_owned(),
mode: bcode_config::AuthMode::ChatGpt,
profile: "legacy-openai".to_owned(),
vault: Some(PathBuf::from("/missing/legacy-vault")),
}),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let legacy = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection::default(),
});
let auth = legacy.auth.expect("legacy auth is host materialized");
assert_eq!(legacy.auth_profile.as_deref(), Some("legacy-openai"));
assert_eq!(auth.profile.as_deref(), Some("legacy-openai"));
assert_eq!(auth.scheme.as_deref(), Some("chatgpt"));
assert!(auth.credentials.is_empty());
assert!(!auth.diagnostics.is_empty());
let explicit = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
auth_profile: Some("missing-explicit".to_owned()),
..bcode_config::ResolvedModelSelection::default()
},
});
assert_eq!(explicit.auth_profile.as_deref(), Some("missing-explicit"));
assert!(explicit.auth.is_none());
}
#[test]
fn request_context_selected_profile_and_pool_precedence_are_characterized() {
let profile = |name: &str| bcode_config::AuthProfileConfig {
backend: "env".to_owned(),
provider_id: Some("openai".to_owned()),
owner_plugin_id: Some("bcode.openai-compatible".to_owned()),
scheme: Some("api_key".to_owned()),
map: BTreeMap::from([(
"api_key".to_owned(),
bcode_config::AuthCredentialMapping {
env: Some(format!("BCODE_TEST_{}_KEY", name.to_ascii_uppercase())),
key: None,
},
)]),
settings: BTreeMap::new(),
};
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
profiles: BTreeMap::from([
("custody-one".to_owned(), profile("custody-one")),
("custody-two".to_owned(), profile("custody-two")),
]),
pools: BTreeMap::from([(
"custody-test-pool".to_owned(),
bcode_config::AuthPoolConfig {
profiles: vec!["custody-one".to_owned(), "custody-two".to_owned()],
preferred_profile: Some("custody-two".to_owned()),
..bcode_config::AuthPoolConfig::default()
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let selected = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
auth_profile: Some("custody-one".to_owned()),
..bcode_config::ResolvedModelSelection::default()
},
});
assert_eq!(selected.auth_profile.as_deref(), Some("custody-one"));
assert_eq!(
selected.auth.and_then(|auth| auth.scheme).as_deref(),
Some("api_key")
);
let pooled = resolve_provider_request_context(ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
auth_profile: Some("custody-one".to_owned()),
auth_pool: Some("custody-test-pool".to_owned()),
..bcode_config::ResolvedModelSelection::default()
},
});
assert_eq!(pooled.auth_candidates.len(), 2);
assert_eq!(pooled.auth_profile.as_deref(), Some("custody-two"));
assert_eq!(
pooled
.auth_candidates
.iter()
.map(|candidate| candidate.profile.as_deref())
.collect::<Vec<_>>(),
vec![Some("custody-two"), Some("custody-one")]
);
}
#[test]
fn explicitly_selected_runtime_account_materializes_only_for_its_owner() {
let config = bcode_config::BcodeConfig::default();
let registry = bcode_config::RuntimeAuthSubscriptions {
profiles: BTreeMap::from([(
"custom-account".to_owned(),
bcode_config::RuntimeAuthProfile {
provider_id: "example".to_owned(),
owner_plugin_id: "example.plugin".to_owned(),
backend: "sshenv".to_owned(),
scheme: "oauth".to_owned(),
storage_profile: "stored-account".to_owned(),
vault: PathBuf::from("/fixture/vault"),
map: BTreeMap::new(),
device_seal: None,
},
)]),
..Default::default()
};
for owner in [None, Some("foreign.plugin"), Some("example.plugin")] {
let profile = runtime_pool_candidate_profile(
&config,
®istry,
"arbitrary-pool",
"custom-account",
owner,
);
assert_eq!(profile.is_some(), owner == Some("example.plugin"));
if let Some(profile) = profile {
assert_eq!(profile.settings["profile"], "stored-account");
}
let mut calls = 0;
let context = resolve_provider_request_context_with_resolver(
ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: owner.map(str::to_owned),
auth_profile: Some("custom-account".to_owned()),
..Default::default()
},
},
®istry,
|name, profile| {
calls += 1;
assert_eq!(name, "custom-account");
assert_eq!(profile.settings["profile"], "stored-account");
ResolvedProviderAuth {
auth: bcode_model::ProviderAuthContext {
scheme: profile.scheme.clone(),
..Default::default()
},
env: BTreeMap::new(),
}
},
);
assert_eq!(calls, usize::from(owner == Some("example.plugin")));
assert_eq!(context.auth.is_some(), owner == Some("example.plugin"));
}
}
#[test]
fn context_pool_ignores_ambient_runtime_members_and_preferences() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("bcode.toml");
std::fs::write(
&path,
r#"
[contexts]
active = "custom"
[contexts.entries.custom.model]
provider_plugin_id = "example.plugin"
auth_pool = "pool"
[contexts.entries.custom.auth.profiles.account]
backend = "env"
provider_id = "example"
owner_plugin_id = "example.plugin"
[contexts.entries.custom.auth.pools.pool]
profiles = ["account"]
"#,
)
.unwrap();
let config = bcode_config::load_config_from_paths(&[path]).unwrap();
let pool = bcode_config::contexts::qualify("custom", "pool").unwrap();
let registry = bcode_config::RuntimeAuthSubscriptions {
pools: BTreeMap::from([(
pool,
bcode_config::RuntimeAuthSubscriptionPool {
preferred_profile: Some("foreign-account".to_owned()),
profiles: vec![bcode_config::RuntimeAuthSubscriptionProfile {
auth_profile: "foreign-account".to_owned(),
..Default::default()
}],
..Default::default()
},
)]),
..Default::default()
};
let mut names = Vec::new();
let context = resolve_provider_request_context_with_resolver(
ProviderRequestContextResolution {
config: &config,
selection: config.resolved_model_selection(),
},
®istry,
|name, _| {
names.push(name.to_owned());
ResolvedProviderAuth::default()
},
);
assert_eq!(names, vec!["ctx-6-custom-account"]);
assert_eq!(context.auth_candidates.len(), 1);
assert_eq!(
context.auth_profile.as_deref(),
Some("ctx-6-custom-account")
);
assert!(!request_needs_runtime_registry(
&ProviderRequestContextResolution {
config: &config,
selection: config.resolved_model_selection()
}
));
}
#[test]
fn strict_pool_owner_must_match_even_when_members_are_compatible() {
let mut config = bcode_config::BcodeConfig::default();
config.auth.profiles.insert(
"account".to_owned(),
bcode_config::AuthProfileConfig {
backend: "env".to_owned(),
owner_plugin_id: Some("example.plugin".to_owned()),
..Default::default()
},
);
config.auth.pools.insert(
"pool".to_owned(),
bcode_config::AuthPoolConfig {
profiles: vec!["account".to_owned()],
provider_plugin_id: Some("example.plugin".to_owned()),
..Default::default()
},
);
let request = ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: Some("example.plugin".to_owned()),
auth_pool: Some("pool".to_owned()),
..Default::default()
},
};
for owner in ["example.plugin", "foreign.plugin", ""] {
let registry = bcode_config::RuntimeAuthSubscriptions {
pools: BTreeMap::from([(
"pool".to_owned(),
bcode_config::RuntimeAuthSubscriptionPool {
owner_plugin_id: Some(owner.to_owned()),
..Default::default()
},
)]),
..Default::default()
};
assert_eq!(
validate_runtime_pool_selection(&request, ®istry).is_ok(),
owner == "example.plugin"
);
}
}
#[test]
fn strict_selection_checks_declared_ownership_for_primary_and_pool_members() {
let mut config = bcode_config::BcodeConfig::default();
config.auth.profiles.insert(
"account".to_owned(),
bcode_config::AuthProfileConfig {
backend: "env".to_owned(),
owner_plugin_id: Some("example.plugin".to_owned()),
..Default::default()
},
);
config.auth.pools.insert(
"pool".to_owned(),
bcode_config::AuthPoolConfig {
profiles: vec!["account".to_owned()],
..Default::default()
},
);
let registry = bcode_config::RuntimeAuthSubscriptions::default();
for owner in [None, Some("foreign.plugin"), Some("example.plugin")] {
let request = ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: owner.map(str::to_owned),
auth_profile: Some("account".to_owned()),
auth_pool: Some("pool".to_owned()),
..Default::default()
},
};
let expected = owner == Some("example.plugin");
assert_eq!(
validate_runtime_account_selection(&request, ®istry).is_ok(),
expected
);
assert_eq!(
validate_runtime_pool_selection(&request, ®istry).is_ok(),
expected
);
}
}
#[test]
fn strict_pool_selection_rejects_missing_empty_and_dangling_members() {
let mut config = bcode_config::BcodeConfig::default();
let selection = bcode_config::ResolvedModelSelection {
auth_pool: Some("custom-pool".to_owned()),
provider_plugin_id: Some("example.plugin".to_owned()),
..Default::default()
};
let registry = bcode_config::RuntimeAuthSubscriptions::default();
assert!(
validate_runtime_pool_selection(
&ProviderRequestContextResolution {
config: &config,
selection: selection.clone()
},
®istry
)
.is_err()
);
config.auth.pools.insert(
"custom-pool".to_owned(),
bcode_config::AuthPoolConfig::default(),
);
assert!(
validate_runtime_pool_selection(
&ProviderRequestContextResolution {
config: &config,
selection: selection.clone()
},
®istry
)
.is_err()
);
config
.auth
.pools
.get_mut("custom-pool")
.unwrap()
.profiles
.push("missing-member".to_owned());
assert!(
validate_runtime_pool_selection(
&ProviderRequestContextResolution {
config: &config,
selection
},
®istry
)
.is_err()
);
}
#[test]
fn strict_resolution_rejects_missing_explicit_account_without_substitution() {
let config = bcode_config::BcodeConfig::default();
let result = try_resolve_with_registry_loader(
ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: Some("example.plugin".to_owned()),
auth_profile: Some("missing-account".to_owned()),
..Default::default()
},
},
|| Ok(bcode_config::RuntimeAuthSubscriptions::default()),
);
assert!(result.is_err());
}
#[test]
fn strict_resolution_propagates_required_registry_failure() {
let config = bcode_config::BcodeConfig::default();
for selection in [
bcode_config::ResolvedModelSelection {
auth_profile: Some("runtime-account".to_owned()),
..Default::default()
},
bcode_config::ResolvedModelSelection {
auth_pool: Some("runtime-pool".to_owned()),
..Default::default()
},
] {
let result = try_resolve_with_registry_loader(
ProviderRequestContextResolution {
config: &config,
selection,
},
|| {
Err(bcode_config::ConfigError::Composition {
message: "registry requires maintenance".to_owned(),
})
},
);
assert!(result.is_err());
}
let context = try_resolve_with_registry_loader(
ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection::default(),
},
|| panic!("unrelated requests must not read auth metadata"),
)
.unwrap();
assert!(context.auth.is_none());
}
#[test]
fn standalone_runtime_selection_loads_registry_without_a_pool() {
let config = bcode_config::BcodeConfig::default();
let mut loaded = false;
let context = resolve_provider_request_context_with_registry_loader(
ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
provider_plugin_id: Some("example.plugin".to_owned()),
auth_profile: Some("enrolled-account".to_owned()),
..Default::default()
},
},
|| {
loaded = true;
bcode_config::RuntimeAuthSubscriptions {
profiles: BTreeMap::from([(
"enrolled-account".to_owned(),
bcode_config::RuntimeAuthProfile {
provider_id: "example".to_owned(),
owner_plugin_id: "example.plugin".to_owned(),
backend: "env".to_owned(),
scheme: "test-scheme".to_owned(),
storage_profile: "stored-account".to_owned(),
vault: PathBuf::from("unused"),
map: BTreeMap::new(),
device_seal: None,
},
)]),
..Default::default()
}
},
);
assert!(loaded);
assert_eq!(context.auth.unwrap().scheme.as_deref(), Some("test-scheme"));
assert!(context.auth_pool.is_none());
}
#[test]
fn request_context_uses_supplied_runtime_subscriptions() {
let config = bcode_config::BcodeConfig::default();
let request = || ProviderRequestContextResolution {
config: &config,
selection: bcode_config::ResolvedModelSelection {
auth_pool: Some("explicit-pool".into()),
provider_plugin_id: Some("example.plugin".into()),
..Default::default()
},
};
let registry = bcode_config::RuntimeAuthSubscriptions {
pools: BTreeMap::from([(
"explicit-pool".into(),
bcode_config::RuntimeAuthSubscriptionPool {
preferred_profile: Some("explicit-profile".into()),
owner_plugin_id: Some("example.plugin".into()),
profiles: vec![bcode_config::RuntimeAuthSubscriptionProfile {
auth_profile: "explicit-profile".into(),
storage_profile: "stored-profile".into(),
vault: PathBuf::from("/fixture/auth.vault"),
provider: "openai".into(),
scheme: "api_key".into(),
..Default::default()
}],
..Default::default()
},
)]),
..Default::default()
};
let mut resolved_profiles = Vec::new();
let controlled = resolve_provider_request_context_with_resolver(
request(),
®istry,
|name, profile| {
resolved_profiles.push(name.to_owned());
assert_eq!(profile.backend, "sshenv");
ResolvedProviderAuth {
auth: bcode_model::ProviderAuthContext {
scheme: profile.scheme.clone(),
..bcode_model::ProviderAuthContext::default()
},
env: BTreeMap::from([("fixture".into(), "controlled".into())]),
}
},
);
assert_eq!(resolved_profiles, ["explicit-profile"]);
assert_eq!(
controlled.env.get("fixture").map(String::as_str),
Some("controlled")
);
assert_eq!(controlled.auth_candidates.len(), 1);
assert_eq!(controlled.auth_profile.as_deref(), Some("explicit-profile"));
assert_eq!(
controlled
.auth
.as_ref()
.and_then(|auth| auth.scheme.as_deref()),
Some("api_key")
);
let empty = resolve_provider_request_context_with_resolver(
request(),
&bcode_config::RuntimeAuthSubscriptions::default(),
|_, _| panic!("empty pool must not acquire profile credentials"),
);
assert!(empty.auth_candidates.is_empty());
assert!(empty.auth.is_none());
}
#[test]
fn request_context_missing_profile_is_explicit_and_does_not_fallback() {
let context = resolve_provider_request_context(ProviderRequestContextResolution {
config: &bcode_config::BcodeConfig::default(),
selection: bcode_config::ResolvedModelSelection {
auth_profile: Some("missing".to_owned()),
..bcode_config::ResolvedModelSelection::default()
},
});
assert_eq!(context.auth_profile.as_deref(), Some("missing"));
assert!(context.auth.is_none());
assert!(context.auth_candidates.is_empty());
assert!(context.env.is_empty());
}
#[test]
fn fresh_provider_lookup_is_unconfigured_without_hiding_dangling_selections() {
assert_eq!(
lookup_auth_provider_profile(
&bcode_config::BcodeConfig::default(),
"exa",
"bcode.web-search",
None,
&bcode_config::RuntimeAuthSubscriptions::default(),
)
.expect("fresh provider lookup"),
AuthProviderProfileLookup::Unconfigured {
profile_name: "exa".to_owned(),
}
);
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
bindings: BTreeMap::from([(
"exa".to_owned(),
bcode_config::AuthBindingConfig {
profile: Some("missing".to_owned()),
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
assert!(matches!(
lookup_auth_provider_profile(
&config,
"exa",
"bcode.web-search",
None,
&bcode_config::RuntimeAuthSubscriptions::default(),
),
Err(AuthProfileResolutionError::MissingProfile { profile, .. })
if profile == "missing"
));
assert!(matches!(
lookup_auth_provider_profile(
&bcode_config::BcodeConfig::default(),
"exa",
"bcode.web-search",
Some("missing"),
&bcode_config::RuntimeAuthSubscriptions::default(),
),
Err(AuthProfileResolutionError::MissingProfile { profile, .. })
if profile == "missing"
));
}
#[test]
fn unowned_declarative_profile_fails_closed() {
for (provider_id, owner_plugin_id, missing) in [
(None, Some("bcode.web-search".to_owned()), "provider_id"),
(Some("exa".to_owned()), None, "owner_plugin_id"),
] {
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
profiles: BTreeMap::from([(
"exa".to_owned(),
bcode_config::AuthProfileConfig {
backend: "sshenv".to_owned(),
provider_id,
owner_plugin_id,
scheme: Some("api_key".to_owned()),
..bcode_config::AuthProfileConfig::default()
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
assert!(matches!(
resolve_auth_provider_profile(
&config,
"exa",
"bcode.web-search",
Some("exa"),
&bcode_config::RuntimeAuthSubscriptions::default(),
),
Err(AuthProfileResolutionError::OwnershipUnverifiable {
missing: actual,
..
}) if actual == missing
));
}
}
fn pool_member_runtime(
member_owner: Option<&str>,
pool_owner: Option<&str>,
) -> bcode_config::RuntimeAuthSubscriptions {
bcode_config::RuntimeAuthSubscriptions {
pools: BTreeMap::from([(
"openai".to_owned(),
bcode_config::RuntimeAuthSubscriptionPool {
provider_plugin_id: pool_owner.map(str::to_owned),
automatic_group: None,
provider_id: None,
owner_plugin_id: None,
preferred_profile: None,
profiles: vec![bcode_config::RuntimeAuthSubscriptionProfile {
auth_profile: "openai-2".to_owned(),
storage_profile: "openai-2".to_owned(),
vault: PathBuf::from("/tmp/openai-vault"),
provider: "openai".to_owned(),
scheme: "chatgpt".to_owned(),
owner_plugin_id: member_owner.map(str::to_owned),
map: BTreeMap::new(),
device_seal: None,
}],
},
)]),
..bcode_config::RuntimeAuthSubscriptions::default()
}
}
#[test]
fn duplicate_legacy_names_require_identical_account_destinations() {
let config = bcode_config::BcodeConfig::default();
let mut runtime = pool_member_runtime(None, Some("bcode.openai-compatible"));
let copy = runtime.pools["openai"].clone();
runtime.pools.insert("another-pool".to_owned(), copy);
assert!(
resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-2"),
&runtime
)
.is_ok()
);
runtime.pools.get_mut("another-pool").unwrap().profiles[0].storage_profile =
"different-account".to_owned();
assert!(matches!(
resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-2"),
&runtime
),
Err(AuthProfileResolutionError::AmbiguousProfile { .. })
));
for pool in ["openai", "another-pool"] {
assert!(
runtime_pool_candidate_profile(
&config,
&runtime,
pool,
"openai-2",
Some("bcode.openai-compatible")
)
.is_none()
);
}
}
#[test]
fn pool_only_candidates_require_verified_owner_before_materialization() {
let config = bcode_config::BcodeConfig::default();
for (member_owner, pool_owner, expected) in [
(None, None, false),
(
Some("foreign.plugin"),
Some("bcode.openai-compatible"),
false,
),
(None, Some("bcode.openai-compatible"), true),
] {
let registry = pool_member_runtime(member_owner, pool_owner);
let (pool_name, pool) = registry.pools.iter().next().unwrap();
let name = &pool.profiles[0].auth_profile;
let resolved = runtime_pool_candidate_profile(
&config,
®istry,
pool_name,
name,
Some("bcode.openai-compatible"),
);
assert_eq!(resolved.is_some(), expected);
assert!(
runtime_pool_candidate_profile(&config, ®istry, pool_name, name, None).is_none()
);
}
}
#[test]
fn profile_discovery_includes_pool_members_and_respects_declarative_shadowing() {
let mut config = bcode_config::BcodeConfig::default();
let mut runtime = pool_member_runtime(None, Some("bcode.openai-compatible"));
runtime.profiles.insert(
"other".into(),
bcode_config::RuntimeAuthProfile {
provider_id: "other-provider".into(),
owner_plugin_id: "other-plugin".into(),
..Default::default()
},
);
let profiles = super::discover_provider_profiles(
&config,
"openai",
"bcode.openai-compatible",
&runtime,
);
assert_eq!(profiles.len(), 2);
assert_eq!(
profiles["openai-2"].as_ref().unwrap().profile_name,
"openai-2"
);
assert!(profiles["other"].is_err());
config.auth.profiles.insert(
"openai-2".into(),
bcode_config::AuthProfileConfig::default(),
);
assert!(
super::discover_provider_profiles(
&config,
"openai",
"bcode.openai-compatible",
&runtime
)["openai-2"]
.is_err()
);
config.auth.profiles.clear();
config.active_context = Some("isolated".into());
assert!(
super::discover_provider_profiles(
&config,
"openai",
"bcode.openai-compatible",
&runtime
)
.is_empty()
);
}
#[test]
fn pool_member_only_runtime_profile_resolves_with_pool_ownership() {
let config = bcode_config::BcodeConfig::default();
let resolved = resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-2"),
&pool_member_runtime(None, Some("bcode.openai-compatible")),
)
.expect("pool member resolves");
assert_eq!(resolved.profile_name, "openai-2");
assert_eq!(resolved.source, AuthProfileSource::Runtime);
assert_eq!(resolved.profile.scheme.as_deref(), Some("chatgpt"));
assert_eq!(
resolved.profile.settings.get("profile").map(String::as_str),
Some("openai-2")
);
assert_eq!(
resolved.profile.settings.get("vault").map(String::as_str),
Some("/tmp/openai-vault")
);
assert!(matches!(
resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-2"),
&pool_member_runtime(Some("bcode.other"), Some("bcode.openai-compatible")),
),
Err(AuthProfileResolutionError::OwnerMismatch { actual, .. })
if actual == "bcode.other"
));
assert!(matches!(
resolve_auth_provider_profile(
&config,
"xai",
"bcode.openai-compatible",
Some("openai-2"),
&pool_member_runtime(None, Some("bcode.openai-compatible")),
),
Err(AuthProfileResolutionError::ProviderMismatch { .. })
));
assert!(matches!(
resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-2"),
&pool_member_runtime(None, None),
),
Err(AuthProfileResolutionError::OwnershipUnverifiable { .. })
));
assert!(matches!(
resolve_auth_provider_profile(
&config,
"openai",
"bcode.openai-compatible",
Some("openai-3"),
&pool_member_runtime(None, Some("bcode.openai-compatible")),
),
Err(AuthProfileResolutionError::MissingProfile { profile, .. })
if profile == "openai-3"
));
}
#[test]
fn runtime_binding_owner_mismatch_fails_closed() {
let runtime = bcode_config::RuntimeAuthSubscriptions {
bindings: BTreeMap::from([(
"exa".to_owned(),
bcode_config::RuntimeAuthBinding {
profile: "exa".to_owned(),
owner_plugin_id: "bcode.other".to_owned(),
},
)]),
profiles: BTreeMap::from([(
"exa".to_owned(),
bcode_config::RuntimeAuthProfile {
provider_id: "exa".to_owned(),
owner_plugin_id: "bcode.web-search".to_owned(),
backend: "sshenv".to_owned(),
scheme: "api_key".to_owned(),
storage_profile: "exa".to_owned(),
vault: PathBuf::from("/tmp/exa-vault"),
map: BTreeMap::new(),
device_seal: None,
},
)]),
..bcode_config::RuntimeAuthSubscriptions::default()
};
assert!(matches!(
resolve_auth_provider_profile(
&bcode_config::BcodeConfig::default(),
"exa",
"bcode.web-search",
None,
&runtime,
),
Err(AuthProfileResolutionError::OwnerMismatch { actual, .. })
if actual == "bcode.other"
));
}
#[test]
fn declarative_binding_precedes_runtime_and_enforces_ownership() {
let declarative_profile = bcode_config::AuthProfileConfig {
backend: "sshenv".to_owned(),
provider_id: Some("exa".to_owned()),
owner_plugin_id: Some("bcode.web-search".to_owned()),
scheme: Some("api_key".to_owned()),
map: BTreeMap::new(),
settings: BTreeMap::new(),
};
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
profiles: BTreeMap::from([("exa-work".to_owned(), declarative_profile)]),
bindings: BTreeMap::from([(
"exa".to_owned(),
bcode_config::AuthBindingConfig {
profile: Some("exa-work".to_owned()),
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let runtime = bcode_config::RuntimeAuthSubscriptions {
bindings: BTreeMap::from([(
"exa".to_owned(),
bcode_config::RuntimeAuthBinding {
profile: "runtime-exa".to_owned(),
owner_plugin_id: "bcode.web-search".to_owned(),
},
)]),
..bcode_config::RuntimeAuthSubscriptions::default()
};
let resolved =
resolve_auth_provider_profile(&config, "exa", "bcode.web-search", None, &runtime)
.expect("declarative binding resolves");
assert_eq!(resolved.profile_name, "exa-work");
assert_eq!(resolved.source, AuthProfileSource::Declarative);
assert!(matches!(
resolve_auth_provider_profile(&config, "exa", "bcode.other", None, &runtime),
Err(AuthProfileResolutionError::OwnerMismatch { .. })
));
}
#[test]
fn runtime_binding_resolves_only_without_declarative_profile() {
let runtime = bcode_config::RuntimeAuthSubscriptions {
bindings: BTreeMap::from([(
"exa".to_owned(),
bcode_config::RuntimeAuthBinding {
profile: "exa".to_owned(),
owner_plugin_id: "bcode.web-search".to_owned(),
},
)]),
profiles: BTreeMap::from([(
"exa".to_owned(),
bcode_config::RuntimeAuthProfile {
provider_id: "exa".to_owned(),
owner_plugin_id: "bcode.web-search".to_owned(),
backend: "sshenv".to_owned(),
scheme: "api_key".to_owned(),
storage_profile: "exa".to_owned(),
vault: PathBuf::from("/vault"),
map: BTreeMap::from([(
"api_key".to_owned(),
bcode_config::AuthCredentialMapping {
env: None,
key: Some("TEST_PROVIDER_API_KEY".to_owned()),
},
)]),
device_seal: Some("off".to_owned()),
},
)]),
..bcode_config::RuntimeAuthSubscriptions::default()
};
let resolved = resolve_auth_provider_profile(
&bcode_config::BcodeConfig::default(),
"exa",
"bcode.web-search",
None,
&runtime,
)
.expect("runtime profile resolves");
assert_eq!(resolved.source, AuthProfileSource::Runtime);
assert_eq!(resolved.profile.provider_id.as_deref(), Some("exa"));
assert_eq!(
resolved
.profile
.settings
.get("device_seal")
.map(String::as_str),
Some("off")
);
assert_eq!(
resolved
.profile
.map
.get("api_key")
.and_then(|mapping| mapping.key.as_deref()),
Some("TEST_PROVIDER_API_KEY")
);
}
#[test]
fn environment_profile_materializes_chatgpt_credentials_without_vault() {
let profile = bcode_config::AuthProfileConfig {
backend: "env".into(),
provider_id: Some("openai".into()),
owner_plugin_id: Some("bcode.openai-compatible".into()),
scheme: Some("chatgpt".into()),
map: BTreeMap::new(),
settings: BTreeMap::from([
("provider".into(), "openai".into()),
("mode".into(), "chatgpt".into()),
(
"env.BCODE_OPENAI_CODEX_ACCESS_TOKEN".into(),
"test-access".into(),
),
(
"env.BCODE_OPENAI_CODEX_ACCOUNT_ID".into(),
"test-account".into(),
),
]),
};
let resolved = resolve_auth_profile("environment-test", &profile);
let auth = resolved.auth;
assert_eq!(auth.scheme.as_deref(), Some("chatgpt"));
assert_eq!(auth.credentials["access_token"].value, "test-access");
assert_eq!(auth.credentials["account_id"].value, "test-account");
assert_eq!(resolved.env["BCODE_OPENAI_AUTH_MODE"], "chatgpt");
}
#[test]
fn mapped_api_key_uses_canonical_credential_name() {
let profile = bcode_config::AuthProfileConfig {
backend: "sshenv".to_string(),
provider_id: None,
owner_plugin_id: None,
scheme: Some("api_key".to_string()),
map: BTreeMap::from([(
"api_key".to_string(),
bcode_config::AuthCredentialMapping {
env: Some("TEST_PROVIDER_KEY".to_string()),
key: None,
},
)]),
settings: BTreeMap::new(),
};
unsafe {
std::env::set_var("TEST_PROVIDER_KEY", "secret");
}
let resolved = resolve_auth_profile("test", &profile);
unsafe {
std::env::remove_var("TEST_PROVIDER_KEY");
}
assert_eq!(
resolved
.auth
.credentials
.get("api_key")
.map(|credential| credential.value.as_str()),
Some("secret")
);
assert_eq!(
resolved
.auth
.storage
.get("api_key")
.map(|storage| storage.key.as_str()),
Some("TEST_PROVIDER_KEY")
);
}
#[test]
fn aws_profile_env_mapping_distinguishes_vault_profile_from_aws_named_profile() {
let mut settings = BTreeMap::from([
("provider".to_string(), "aws".to_string()),
("profile".to_string(), "bedrock".to_string()),
]);
let mut env = BTreeMap::new();
let chain_profile = bcode_config::AuthProfileConfig {
backend: "aws_default_chain".to_string(),
settings: settings.clone(),
..bcode_config::AuthProfileConfig::default()
};
merge_settings_env(&chain_profile, &mut env);
assert_eq!(env.get("AWS_PROFILE").map(String::as_str), Some("bedrock"));
assert_eq!(
env.get("BCODE_BEDROCK_AWS_PROFILE").map(String::as_str),
Some("bedrock")
);
let mut env = BTreeMap::new();
let vault_profile = bcode_config::AuthProfileConfig {
backend: "sshenv".to_string(),
settings: settings.clone(),
..bcode_config::AuthProfileConfig::default()
};
merge_settings_env(&vault_profile, &mut env);
assert!(!env.contains_key("AWS_PROFILE"));
assert!(!env.contains_key("BCODE_BEDROCK_AWS_PROFILE"));
settings.insert("aws_profile".to_string(), "america-admin".to_string());
let mut env = BTreeMap::new();
let vault_profile = bcode_config::AuthProfileConfig {
backend: "sshenv".to_string(),
settings,
..bcode_config::AuthProfileConfig::default()
};
merge_settings_env(&vault_profile, &mut env);
assert_eq!(
env.get("AWS_PROFILE").map(String::as_str),
Some("america-admin")
);
}
#[test]
fn openai_pool_priming_defers_window_defaults_to_provider() {
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
pools: BTreeMap::from([(
"openai".to_string(),
bcode_config::AuthPoolConfig {
provider_plugin_id: Some("bcode.openai-compatible".to_string()),
priming: bcode_config::AuthPoolPrimingConfig {
enabled: true,
..bcode_config::AuthPoolPrimingConfig::default()
},
..bcode_config::AuthPoolConfig::default()
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let routing = selected_auth_pool_routing(&config, Some("openai"));
assert!(routing.priming_enabled);
assert!(routing.priming_required_windows.is_empty());
}
#[test]
fn explicit_priming_windows_override_openai_defaults() {
let config = bcode_config::BcodeConfig {
auth: bcode_config::AuthConfig {
pools: BTreeMap::from([(
"openai".to_string(),
bcode_config::AuthPoolConfig {
provider_plugin_id: Some("bcode.openai-compatible".to_string()),
priming: bcode_config::AuthPoolPrimingConfig {
required_windows: BTreeMap::from([(
"custom".to_string(),
vec!["daily".to_string()],
)]),
..bcode_config::AuthPoolPrimingConfig::default()
},
..bcode_config::AuthPoolConfig::default()
},
)]),
..bcode_config::AuthConfig::default()
},
..bcode_config::BcodeConfig::default()
};
let routing = selected_auth_pool_routing(&config, Some("openai"));
assert_eq!(
routing.priming_required_windows,
BTreeMap::from([("custom".to_string(), vec!["daily".to_string()])])
);
}
}