diff --git a/docs/architecture/compat-cleanup-register.md b/docs/architecture/compat-cleanup-register.md
index 26d8e51d0..6b9d66301 100644
--- a/docs/architecture/compat-cleanup-register.md
+++ b/docs/architecture/compat-cleanup-register.md
@@ -12,7 +12,9 @@ for later deletion.
## Open Items
-No compatibility code is currently registered.
+- `CTX-002`
+ - Why: admin OIDC and STS consumers now resolve through `resolve_oidc_handle()`, but that resolver still falls back to legacy global OIDC state while the AppContext-owned OIDC wiring is being completed.
+ - Remove after: OIDC ownership is initialized and consumed through AppContext end-to-end, and `resolve_oidc_handle()` no longer needs the legacy global fallback path.
## Review Checklist
diff --git a/rustfs/src/admin/console.rs b/rustfs/src/admin/console.rs
index 0e9a0aaec..8ec26d1c9 100644
--- a/rustfs/src/admin/console.rs
+++ b/rustfs/src/admin/console.rs
@@ -136,6 +136,7 @@ impl Config {
let http_prefix = rustfs_config::RUSTFS_HTTP_PREFIX;
// Collect OIDC provider info if available
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let oidc = resolve_oidc_handle()
.map(|sys| {
sys.list_visible_providers()
diff --git a/rustfs/src/admin/handlers/oidc.rs b/rustfs/src/admin/handlers/oidc.rs
index d1b0acdcb..d3419f290 100644
--- a/rustfs/src/admin/handlers/oidc.rs
+++ b/rustfs/src/admin/handlers/oidc.rs
@@ -268,6 +268,7 @@ pub struct ListOidcProvidersHandler {}
#[async_trait::async_trait]
impl Operation for ListOidcProvidersHandler {
async fn call(&self, _req: S3Request
, _params: Params<'_, '_>) -> S3Result> {
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let providers = oidc_sys.list_visible_providers();
@@ -439,6 +440,7 @@ impl Operation for OidcAuthorizeHandler {
return Err(s3_error!(InvalidRequest, "invalid provider_id"));
}
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
// Derive the callback redirect URI from the request
@@ -512,6 +514,7 @@ impl Operation for OidcCallbackHandler {
));
}
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let redirect_uri = derive_callback_uri(&req, provider_id)?;
@@ -599,6 +602,7 @@ impl Operation for OidcLogoutHandler {
return redirect_response(&fallback_location);
};
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let location = match resolve_oidc_handle() {
Some(oidc_sys) => match oidc_sys.build_logout_url(&logout_token, &fallback_location).await {
Ok(Some(url)) => url,
@@ -628,6 +632,7 @@ impl Operation for OidcLogoutHandler {
/// an explicit redirect_uri is recommended to prevent header manipulation.
fn derive_callback_uri(req: &S3Request, provider_id: &str) -> S3Result {
// Use explicitly configured redirect_uri if available
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
if let Some(oidc_sys) = resolve_oidc_handle()
&& let Some(config) = oidc_sys.get_provider_config(provider_id)
{
diff --git a/rustfs/src/admin/handlers/sts.rs b/rustfs/src/admin/handlers/sts.rs
index 9b608b2b9..6ff1e0206 100644
--- a/rustfs/src/admin/handlers/sts.rs
+++ b/rustfs/src/admin/handlers/sts.rs
@@ -59,6 +59,7 @@ fn has_identity_authorization_context(policies: &[String], groups: &[String]) ->
}
fn configured_roles_claim_key(provider_id: &str) -> Option {
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
resolve_oidc_handle()
.as_ref()
.and_then(|oidc_sys| oidc_sys.get_provider_config(provider_id))
@@ -316,6 +317,7 @@ async fn handle_assume_role_with_web_identity(body: AssumeRoleRequest) -> S3Resu
}
// Verify the JWT and extract claims
+ // RUSTFS_COMPAT_TODO(CTX-002): admin OIDC consumers still depend on the resolver's global fallback while AppContext OIDC wiring is incomplete. Remove after OIDC ownership moves fully into AppContext and the global fallback is retired.
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let (claims, provider_id) = oidc_sys
diff --git a/rustfs/src/admin/service/config.rs b/rustfs/src/admin/service/config.rs
index 592b8edb3..c4e85b7d2 100644
--- a/rustfs/src/admin/service/config.rs
+++ b/rustfs/src/admin/service/config.rs
@@ -15,7 +15,8 @@
use super::super::storageclass;
use super::super::{STORAGE_CLASS_SUB_SYS, read_admin_config_without_migrate};
use crate::app::context::{
- publish_server_config, publish_storage_class_config, resolve_notification_system, resolve_object_store_handle,
+ AppContext, get_global_app_context, publish_server_config, publish_storage_class_config, resolve_notification_system,
+ resolve_object_store_handle, resolve_object_store_handle_for_context,
};
use rustfs_audit::reload_audit_config;
use rustfs_config::audit::{AUDIT_MQTT_SUB_SYS, AUDIT_REDIS_DEFAULT_CHANNEL, AUDIT_WEBHOOK_SUB_SYS};
@@ -66,6 +67,10 @@ fn invalid_request(message: impl Into) -> S3Error {
S3Error::with_message(S3ErrorCode::InvalidRequest, message.into())
}
+fn resolve_runtime_config_store_for_context(context: Option<&AppContext>) -> S3Result> {
+ resolve_object_store_handle_for_context(context).ok_or_else(|| internal_error("storage layer not initialized"))
+}
+
async fn apply_storage_class_runtime_config(config: &ServerConfig) -> S3Result<()> {
let Some(store) = resolve_object_store_handle() else {
return Err(internal_error("storage layer not initialized"));
@@ -292,14 +297,12 @@ pub async fn apply_dynamic_config_for_subsystem(config: &ServerConfig, sub_syste
Ok(true)
}
-pub async fn reload_dynamic_config_runtime_state(sub_system: &str) -> S3Result<()> {
+pub async fn reload_dynamic_config_runtime_state_for_context(context: Option<&AppContext>, sub_system: &str) -> S3Result<()> {
if !is_dynamic_config_subsystem(sub_system) {
return Err(internal_error(format!("unsupported dynamic config subsystem: {sub_system}")));
}
- let Some(store) = resolve_object_store_handle() else {
- return Err(internal_error("storage layer not initialized"));
- };
+ let store = resolve_runtime_config_store_for_context(context)?;
let config = read_admin_config_without_migrate(store).await.map_err(|err| {
warn!("peer reload_dynamic_config: failed to load server config for {sub_system}: {err}");
@@ -312,10 +315,13 @@ pub async fn reload_dynamic_config_runtime_state(sub_system: &str) -> S3Result<(
Ok(())
}
-pub async fn reload_runtime_config_snapshot() -> S3Result<()> {
- let Some(store) = resolve_object_store_handle() else {
- return Err(internal_error("storage layer not initialized"));
- };
+pub async fn reload_dynamic_config_runtime_state(sub_system: &str) -> S3Result<()> {
+ let context = get_global_app_context();
+ reload_dynamic_config_runtime_state_for_context(context.as_deref(), sub_system).await
+}
+
+pub async fn reload_runtime_config_snapshot_for_context(context: Option<&AppContext>) -> S3Result<()> {
+ let store = resolve_runtime_config_store_for_context(context)?;
let config = read_admin_config_without_migrate(store).await.map_err(|err| {
warn!("peer reload_runtime_config_snapshot: failed to load server config: {err}");
@@ -340,6 +346,11 @@ pub async fn reload_runtime_config_snapshot() -> S3Result<()> {
Ok(())
}
+pub async fn reload_runtime_config_snapshot() -> S3Result<()> {
+ let context = get_global_app_context();
+ reload_runtime_config_snapshot_for_context(context.as_deref()).await
+}
+
pub async fn signal_dynamic_config_reload(sub_system: &str) {
if !is_dynamic_config_subsystem(sub_system) {
return;
diff --git a/rustfs/src/admin/service/site_replication.rs b/rustfs/src/admin/service/site_replication.rs
index b9145d629..896179713 100644
--- a/rustfs/src/admin/service/site_replication.rs
+++ b/rustfs/src/admin/service/site_replication.rs
@@ -15,7 +15,7 @@
use super::super::Error as StorageError;
use super::super::{read_admin_config, save_admin_config};
use crate::admin::site_replication_identity::{deployment_id_for_endpoint, normalize_peer_map_by_identity_with};
-use crate::app::context::resolve_object_store_handle;
+use crate::app::context::{AppContext, get_global_app_context, resolve_object_store_handle_for_context};
use rustfs_madmin::PeerInfo;
use s3s::{S3Error, S3ErrorCode, S3Result};
use serde_json::{Map, Value};
@@ -90,8 +90,8 @@ fn normalize_site_replication_state_json(data: &[u8]) -> Result