github-actions[bot] commented on code in PR #66004:
URL: https://github.com/apache/doris/pull/66004#discussion_r3654979415


##########
fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java:
##########
@@ -150,14 +152,14 @@ public static LocationPath of(String location,
             schema = Strings.nullToEmpty(uri.getScheme());
         }
 
-        return new LocationPath(schema, normalizedLocation, fsIdentifier, 
storageProperties);
+        return new LocationPath(schema, normalizedLocation, fsIdentifier, 
storageAdapter);
     }
 
-    public static StorageProperties.Type fromSchemaWithContext(String 
location, String schema) {
+    public static StorageTypeId fromSchemaWithContext(String location, String 
schema) {
         if (isHdfsOnOssEndpoint(location)) {
-            return StorageProperties.Type.OSS_HDFS;
+            return StorageTypeId.OSS_HDFS;
         }
-        return SchemaTypeMapper.fromSchema(schema); // fallback to default
+        return StorageRegistry.fromScheme(schema); // fallback to default

Review Comment:
   [P1] Route provider-declared schemes through the bound adapter
   
   `FileSystemProperties.getSupportedSchemes()` is documented as the routing 
source of truth, and the new typed `XyzProps` test advertises `{s3, xyz}`, but 
this lookup only knows the static built-in registry. For `xyz://bucket/key`, 
`type` is null, `findStorageAdapter` misses every fallback, and resolution 
fails before the typed provider can normalize or create a filesystem; the later 
BE/filesystem type lookups are static as well. This makes the advertised 
zero-fe-core-change native scheme unusable through catalogs and 
`SpiSwitchingFileSystem`. Please resolve unknown schemes from the bound 
adapters and carry the selected adapter's S3-family type through 
normalization/BE dispatch, with an end-to-end `xyz://` test.



##########
fe/fe-filesystem/fe-filesystem-hdfs-base/src/main/java/org/apache/doris/filesystem/hdfs/properties/HdfsCompatibleProperties.java:
##########
@@ -85,6 +267,11 @@ protected void checkRequiredProperties() {
                 }
             }
         }
+        if ("kerberos".equalsIgnoreCase(hdfsAuthenticationType) && 
(Strings.isNullOrEmpty(hdfsKerberosPrincipal)

Review Comment:
   [P1] Keep OSS-HDFS out of the shared Kerberos validation
   
   In the legacy hierarchy, the Kerberos fields, validation, and authenticator 
selection lived only in `HdfsProperties`; `OSSHdfsProperties` inherited the 
simple-auth base. `OssHdfsProperties` now inherits this check unconditionally, 
so an otherwise valid OSS-HDFS catalog carrying a shared 
`hadoop.security.authentication=kerberos` setting fails unless HDFS 
principal/keytab values are present, and with those values it selects a 
Kerberos authenticator instead of the legacy simple one. This also contradicts 
the JFS-thread follow-up that OSS-HDFS stayed unchanged. Please make the shared 
auth model opt-in for HDFS/JFS (or explicitly opt OSS-HDFS out) and add an 
OSS-HDFS parity case.



##########
fe/fe-core/src/main/java/org/apache/doris/fs/FileSystemPluginManager.java:
##########
@@ -141,6 +158,197 @@ public void registerProvider(FileSystemProvider provider) 
{
         
DatasourcePrintableMap.registerSensitiveKeys(provider.sensitivePropertyKeys());
     }
 
+    /** Returns an unmodifiable view of the loaded providers, in registration 
order. */
+    public List<FileSystemProvider> getProviders() {
+        return Collections.unmodifiableList(providers);
+    }
+
+    /**
+     * Selection priority for raw-user-props binding = {@code 
StorageRegistry.Provider} declaration
+     * order (single source of truth; mirrors the legacy 
StorageProperties.PROVIDERS registry
+     * order, with JFS hoisted ahead of HDFS so a jfs:// uri beats HDFS's 
key-hint guess).
+     */
+
+    private static final String DEPRECATED_OSS_HDFS_SUPPORT = 
"oss.hdfs.enabled";
+
+    /**
+     * Binds the first matching provider for raw user properties, mirroring
+     * {@code StorageProperties.createPrimary}: fixed priority, explicit 
{@code fs.<x>.support}
+     * flags first-class, heuristic guesses globally disabled once any 
explicit flag is present,
+     * and no default fallback — throws when nothing matches.
+     */
+    public FileSystemProperties bindPrimary(Map<String, String> properties) {
+        boolean useGuess = !hasAnyExplicitFsSupport(properties);
+        Map<String, String> probeView = withProbeContext(properties);
+        for (StorageRegistry.Provider meta : 
StorageRegistry.Provider.values()) {
+            FileSystemProvider provider = providerByName(meta.name());
+            if (provider == null) {
+                continue;
+            }
+            if (provider.supportsExplicit(properties) || (useGuess && 
provider.supportsGuess(probeView))) {
+                return provider.bind(properties);
+            }
+        }
+        // Providers not in the StorageRegistry.Provider table (out-of-tree 
plugins) are consulted
+        // after every known provider, in registration order — adding a plugin 
needs no fe-core
+        // priority-list change and can never preempt the built-in set.
+        for (FileSystemProvider provider : providers) {
+            if (StorageRegistry.Provider.byName(provider.name()).isPresent()) {
+                continue;
+            }
+            FileSystemProperties bound = tryBindUnlisted(provider, properties, 
useGuess, probeView);
+            if (bound != null) {
+                return bound;
+            }
+        }
+        throw new StoragePropertiesException(
+                "No supported storage type found. Please check your 
configuration."
+                        + " Loaded filesystem providers: " + providerNames());
+    }
+
+    /**
+     * Binds every matching provider for raw user properties, mirroring
+     * {@code StorageProperties.createAll}: multi-hit is allowed, OSS-HDFS and 
OSS stay mutually
+     * exclusive, and when no explicit flag is present and no HDFS-family 
provider matched, a
+     * default HDFS binding is inserted at index 0 (fe-core's default-HDFS 
fallback).
+     */
+    public List<FileSystemProperties> bindAll(Map<String, String> properties) {
+        boolean useGuess = !hasAnyExplicitFsSupport(properties);
+        Map<String, String> probeView = withProbeContext(properties);
+        List<FileSystemProperties> result = new ArrayList<>();
+        boolean ossHdfsMatched = false;
+        boolean hdfsFamilyMatched = false;
+        for (StorageRegistry.Provider meta : 
StorageRegistry.Provider.values()) {
+            FileSystemProvider provider = providerByName(meta.name());
+            if (provider == null) {
+                continue;
+            }
+            if (meta == StorageRegistry.Provider.OSS && ossHdfsMatched) {
+                continue;
+            }
+            // JFS and HDFS were ONE typed class in fe-core (jfs rode 
HdfsProperties), so a map
+            // matching both (jfs fs.defaultFS also trips HDFS's key-hint 
guess) produced a
+            // single instance there. The plugin split makes them two 
providers sharing
+            // StorageTypeId.HDFS — bind at most one or the type-keyed catalog 
map collides
+            // ("Duplicate storage type: HDFS", caught by external_table_p0 
jfs catalog tests).
+            if (meta == StorageRegistry.Provider.HDFS && hdfsFamilyMatched) {
+                continue;
+            }
+            if (provider.supportsExplicit(properties) || (useGuess && 
provider.supportsGuess(probeView))) {
+                result.add(provider.bind(properties));
+                if (meta == StorageRegistry.Provider.OSS_HDFS) {
+                    ossHdfsMatched = true;
+                }
+                // fe-core adds its default-HDFS fallback only when no 
HdfsProperties instance is
+                // in the result; jfs rode HdfsProperties there, so JFS counts 
as HDFS here.
+                // OSS_HDFS does NOT: fe-core's OSSHdfsProperties is not an 
HdfsProperties, and
+                // createAll happily prepends the HDFS fallback next to it.
+                if (meta == StorageRegistry.Provider.HDFS || meta == 
StorageRegistry.Provider.JFS) {
+                    hdfsFamilyMatched = true;
+                }
+            }
+        }
+        // Unlisted (out-of-tree) providers append after the known set, 
registration order.
+        for (FileSystemProvider provider : providers) {
+            if (StorageRegistry.Provider.byName(provider.name()).isPresent()) {
+                continue;
+            }
+            FileSystemProperties bound = tryBindUnlisted(provider, properties, 
useGuess, probeView);
+            if (bound != null) {
+                result.add(bound);
+            }
+        }
+        if (useGuess && !hdfsFamilyMatched) {
+            FileSystemProvider hdfs = providerByName("HDFS");
+            if (hdfs != null) {
+                result.add(0, hdfs.bind(properties));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Probe-context key carrying {@code Config.azure_blob_host_suffixes} into 
provider guess
+     * probes. Providers cannot see fe-core Config; routing runs here in 
fe-core, so the live
+     * (admin-extensible) suffix list is injected into a COPY used only for
+     * {@code supportsGuess} probes — the map handed to {@code bind()} stays 
untouched, so the
+     * marker can never leak into bound properties, backend maps, or persisted 
state.
+     */
+    public static final String AZURE_HOST_SUFFIXES_PROBE_KEY = 
"_AZURE_HOST_SUFFIXES_";
+
+    /**
+     * Typed-binding attempt for a provider outside the built-in registry. 
Typed providers
+     * (supportsExplicit/supportsGuess + bind) are selected normally. A legacy 
raw-SPI provider
+     * — one that only implements {@code supports(Map)}/{@code create(Map)} 
and inherits the
+     * throwing default {@code bind()} — is still CONSULTED via {@code 
supports()} so it is not
+     * silently invisible, but typed binding cannot represent it: it is 
rejected with an
+     * actionable warning (implement the typed hooks) and remains usable 
through the raw
+     * {@code createFileSystem(Map)} entry point.
+     */
+    private FileSystemProperties tryBindUnlisted(FileSystemProvider provider,
+            Map<String, String> properties, boolean useGuess, Map<String, 
String> probeView) {
+        boolean typedMatch = provider.supportsExplicit(properties)
+                || (useGuess && provider.supportsGuess(probeView));
+        if (!typedMatch) {
+            if (useGuess && provider.supports(properties)) {
+                LOG.warn("Filesystem provider '{}' matches these properties 
via the legacy"
+                        + " supports() hook only; typed binding requires 
supportsExplicit/"
+                        + "supportsGuess/bind — implement them to make the 
provider selectable"
+                        + " for catalogs/TVFs. It remains usable via 
createFileSystem(Map).",
+                        provider.name());
+            }
+            return null;
+        }
+        try {
+            return provider.bind(properties);
+        } catch (UnsupportedOperationException e) {
+            LOG.warn("Filesystem provider '{}' matched but does not implement 
typed bind();"
+                    + " skipping it for typed binding. Implement bind() to 
integrate with the"
+                    + " storage facade.", provider.name());
+            return null;
+        }
+    }
+
+    /** Builds the guess-probe view of the raw properties (see {@link 
#AZURE_HOST_SUFFIXES_PROBE_KEY}). */
+    public static Map<String, String> withProbeContext(Map<String, String> 
properties) {
+        String[] suffixes = Config.azure_blob_host_suffixes;
+        if (suffixes == null || suffixes.length == 0) {
+            return properties;
+        }
+        Map<String, String> probeView = new HashMap<>(properties);
+        probeView.put(AZURE_HOST_SUFFIXES_PROBE_KEY, String.join(",", 
suffixes));
+        return probeView;
+    }
+
+    private FileSystemProvider providerByName(String name) {
+        for (FileSystemProvider p : providers) {
+            if (name.equalsIgnoreCase(p.name())) {
+                return p;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * True when the user explicitly declared any filesystem via {@code 
fs.<x>.support=true} or the
+     * legacy {@code oss.hdfs.enabled=true}. Counterpart of
+     * {@code StorageProperties.hasAnyExplicitFsSupport}; checked structurally 
so this class never
+     * needs the full dialect list.
+     */
+    private static boolean hasAnyExplicitFsSupport(Map<String, String> 
properties) {
+        if 
("true".equalsIgnoreCase(properties.getOrDefault(DEPRECATED_OSS_HDFS_SUPPORT, 
"false"))) {
+            return true;
+        }
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            String key = entry.getKey();
+            if (key != null && key.startsWith("fs.") && 
key.endsWith(".support")

Review Comment:
   [P1] Do not let unrelated fs.* flags suppress GCS guessing
   
   This structural wildcard treats `fs.gs.support=true` as an explicit Doris 
provider selection, but `GcsFileSystemProvider.supportsExplicit()` recognizes 
only `fs.gcs.support`. The existing Paimon catalog regression uses 
`fs.gs.support=true` with both native GCS and S3-style Google endpoints; the 
legacy gate did not recognize that spelling, so endpoint guessing still 
selected GCS. Here the wildcard disables every guess and leaves those catalogs 
with no GCS binding. Please derive the gate from providers that actually report 
`supportsExplicit(properties)`, or preserve/alias the recognized compatibility 
keys, and cover both exercised `fs.gs.support` cases.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageUriUtils.java:
##########
@@ -0,0 +1,360 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.storage;
+
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.S3URI;
+import org.apache.doris.foundation.property.StoragePropertiesException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+/**
+ * fe-core-only URI validation/normalization glue for {@link StorageAdapter}.
+ *
+ * <p>Ports the URI subset of the legacy {@code S3PropertyUtils} and {@code 
AzurePropertyUtils}
+ * pure functions so the facade does not depend on the to-be-deleted
+ * legacy typed storage package (deleted in Phase D). Behavior is kept 
identical, with one deliberate
+ * exception: the legacy full-parse path could throw the checked {@code 
UserException} (from
+ * {@link S3URI#create}); the facade surface is unchecked, so that exception 
is rethrown as
+ * {@link StoragePropertiesException} with the same message.</p>
+ */
+public final class StorageUriUtils {
+
+    private static final String URI_KEY = "uri";
+    private static final String SCHEME_DELIM = "://";
+    private static final String S3_SCHEME_PREFIX = "s3://";
+
+    // S3-compatible schemes that can be converted to s3:// with simple string 
replacement
+    // Format: scheme://bucket/key -> s3://bucket/key
+    private static final String[] SIMPLE_S3_COMPATIBLE_SCHEMES = {
+            "s3a", "s3n", "oss", "cos", "cosn", "obs", "bos", "gs"
+    };
+
+    private static final Pattern ONELAKE_PATTERN = Pattern.compile(
+            
"abfs[s]?://([^@]+)@([^/]+)\\.dfs\\.fabric\\.microsoft\\.com(/.*)?", 
Pattern.CASE_INSENSITIVE);
+
+    private StorageUriUtils() {
+    }
+
+    /**
+     * Port of legacy {@code S3PropertyUtils.validateAndNormalizeUri}: 
validates and normalizes
+     * the given path into a standard {@code s3://bucket/key} URI.
+     *
+     * <p>Also carries the legacy {@code 
OSSProperties.validateAndNormalizeUri} override when
+     * {@code applyOssBucketRewrite} is true (the caller's binding is the OSS 
provider): a
+     * virtual-hosted {@code scheme://bucket.endpoint/key} authority is 
rewritten to
+     * {@code scheme://bucket/key} before the base normalization, 
byte-identical to the legacy
+     * {@code rewriteOssBucketIfNecessary}. Non-OSS bindings never rewrite — a 
dotted bucket
+     * name under an explicit S3 binding (e.g. {@code oss://logs.prod/…} with
+     * {@code fs.s3.support=true}) passes through unchanged, exactly like 
legacy.</p>
+     */
+    public static String validateAndNormalizeS3Uri(String path,
+            String stringUsePathStyle,
+            String stringForceParsingByStandardUri,
+            boolean applyOssBucketRewrite) {
+        if (StringUtils.isBlank(path)) {
+            throw new StoragePropertiesException("path is null");
+        }
+
+        // Legacy OSSProperties applied the bucket-domain rewrite before the 
base normalization.
+        if (applyOssBucketRewrite) {
+            path = rewriteOssBucketIfNecessary(path);
+        }
+
+        // Fast path 1: s3:// paths are already in the normalized format 
expected by BE
+        if (path.startsWith(S3_SCHEME_PREFIX)) {
+            return path;
+        }
+
+        // Fast path 2: simple S3-compatible schemes (oss://, cos://, s3a://, 
etc.)
+        // can be converted with simple string replacement: 
scheme://bucket/key -> s3://bucket/key
+        String normalized = trySimpleSchemeConversion(path);
+        if (normalized != null) {
+            return normalized;
+        }
+
+        // Full parsing path: for HTTP URLs and other complex formats
+        boolean usePathStyle = Boolean.parseBoolean(stringUsePathStyle);
+        boolean forceParsingByStandardUri = 
Boolean.parseBoolean(stringForceParsingByStandardUri);
+        S3URI s3uri;
+        try {
+            s3uri = S3URI.create(path, usePathStyle, 
forceParsingByStandardUri);
+        } catch (UserException e) {
+            // Facade surface is unchecked; keep the legacy message.
+            throw new StoragePropertiesException(e.getMessage(), e);

Review Comment:
   [P2] Preserve user-error classification for malformed S3 URLs
   
   The legacy S3 helper let `S3URI.create()` propagate `UserException`, and 
`BrokerDesc.getFileLocation()` still advertises that checked contract. Wrapping 
it here changes a reachable REST path: `ImportAction.fileReview()` catches only 
`UserException` and returns `okWithCommonError`, so a malformed full-parse 
location such as `not a uri` now escapes to the generic exception handler as an 
internal error. Please preserve/translate the checked user error at the facade 
or broker boundary (or update every checked-error consumer), and cover the 
file-review response.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java:
##########
@@ -0,0 +1,878 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.storage;
+
+import org.apache.doris.common.Config;
+import 
org.apache.doris.datasource.property.common.AwsCredentialsProviderFactory;
+import org.apache.doris.datasource.property.common.AwsCredentialsProviderMode;
+import org.apache.doris.filesystem.properties.FileSystemProperties;
+import org.apache.doris.filesystem.properties.HadoopStorageProperties;
+import org.apache.doris.filesystem.properties.S3CompatibleFileSystemProperties;
+import org.apache.doris.filesystem.spi.FileSystemProvider;
+import org.apache.doris.foundation.property.StoragePropertiesException;
+import org.apache.doris.foundation.security.ExecutionAuthenticator;
+import org.apache.doris.fs.FileSystemPluginManager;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import 
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sts.StsClient;
+import 
software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * fe-core facade over the fe-filesystem SPI typed properties (Phase B3 of the
+ * StorageProperties removal plan).
+ *
+ * <p>One adapter wraps one {@link FileSystemProperties} binding produced by
+ * {@link FileSystemPluginManager#bindPrimary}/{@code bindAll}. Its public 
surface mirrors the
+ * legacy typed storage-properties contract exactly — backend map,
+ * Hadoop configuration, storage name, schemas, type — so consumers can 
migrate mechanically.
+ * Every known SPI-vs-fe-core drift from the master plan's §2.4 parity ledger 
is reconciled here
+ * (or in the SPI implementation, where noted); each reconciliation carries a
+ * "align fe-core" comment referencing the ledger item.</p>
+ */
+public final class StorageAdapter {
+
+    private static final Logger LOG = 
LogManager.getLogger(StorageAdapter.class);
+
+    /**
+     * Plugin manager injected at FE startup (see {@code 
FileSystemFactory.initPluginManager},
+     * which forwards here). Held locally so the facade does not reach back 
into the {@code fs}
+     * runtime layer — the dependency direction is fs → datasource.storage 
only.
+     */
+    private static volatile FileSystemPluginManager pluginManager;
+
+    /**
+     * Lazy fallback registry for environments where FE startup did not run
+     * {@link #initPluginManager} (unit tests, migration phase).
+     */
+    private static volatile FileSystemPluginManager fallbackManager;
+
+    /** Called once at FE startup, before any {@code of}/{@code ofAll} 
binding. */
+    public static void initPluginManager(FileSystemPluginManager manager) {
+        pluginManager = manager;
+    }
+
+    private static final String SIMPLE_AWS_CREDENTIALS_PROVIDER =
+            "org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider";
+    private static final String ASSUMED_ROLE_CREDENTIAL_PROVIDER =
+            "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider";
+
+    /**
+     * Hadoop keys the facade re-derives for the S3 provider instead of taking 
the SPI values:
+     * fe-core gates them on accessKey-blank and on 
Config.aws_credentials_provider_version,
+     * which the SPI layer cannot see (align fe-core, ledger 2.4-3).
+     */
+    private static final Set<String> S3_CREDENTIAL_KEYS = ImmutableSet.of(
+            "fs.s3a.aws.credentials.provider",
+            "fs.s3a.assumed.role.arn",
+            "fs.s3a.assumed.role.credentials.provider",
+            "fs.s3a.assumed.role.external.id");
+
+    /**
+     * Legacy per-dialect alias prefix for {@code 
force_parsing_by_standard_uri} (fe-core
+     * {@code AbstractS3CompatibleProperties} subclasses declared
+     * {@code <prefix>.force_parsing_by_standard_uri} plus the generic key; 
plain S3 had the
+     * generic key only). Read from the raw props because the SPI api 
interface does not expose
+     * this flag.
+     */
+    private static final Map<String, String> FORCE_PARSING_PREFIXES = 
ImmutableMap.<String, String>builder()
+            .put("OSS", "oss")
+            .put("COS", "cos")
+            .put("OBS", "obs")
+            .put("GCS", "gs")
+            .put("MINIO", "minio")
+            .put("OZONE", "ozone")
+            .build();
+
+    private final FileSystemProperties spi;
+    private final Map<String, String> origProps;
+    private final String providerKey;
+    private final StorageTypeId type;
+    /** Explicit broker name for adapters built via {@link #ofBroker}; mirrors 
the legacy
+     *  {@code BrokerProperties.of(name, props)} where the name is NOT part of 
the raw props. */
+    private final String brokerNameOverride;
+    /** fe-core S3Properties credentials-provider mode; only resolved for the 
S3 provider. */
+    private final AwsCredentialsProviderMode s3CredentialsMode;
+    /** Resolved once at construction (legacy bound it at init); read per file 
in listing loops. */
+    private final String forceParsingByStandardUriValue;
+    /**
+     * Lazily built (legacy {@code X.of()} factories never built one; {@code 
new Configuration()}
+     * re-parses the Hadoop XML defaults and is too expensive for 
per-statement bindings that
+     * never read it, e.g. cloud COPY INTO). Volatile double-checked; 
construction is a pure
+     * function of final fields.
+     */
+    private volatile Configuration hadoopStorageConfig;
+    private volatile boolean hadoopStorageConfigBuilt;
+    /**
+     * Adapters are shared across threads (catalog adapter map, FS caches), so 
the lazily cached
+     * map must be safely published — legacy classes were immune (eager init 
or fresh map per
+     * call); the cache is a facade addition.
+     */
+    private volatile Map<String, String> backendConfigProperties;
+
+    private StorageAdapter(FileSystemProperties spi, Map<String, String> 
origProps) {
+        this(spi, origProps, null);
+    }
+
+    private StorageAdapter(FileSystemProperties spi, Map<String, String> 
origProps, String brokerNameOverride) {
+        this.spi = spi;
+        this.origProps = origProps;
+        this.brokerNameOverride = brokerNameOverride;
+        this.providerKey = spi.providerName().toUpperCase(Locale.ROOT);
+        // Providers outside the StorageRegistry.Provider table (out-of-tree 
plugins): an
+        // S3-compatible dialect joins the S3 family id so every 
TypeId-dispatched consumer
+        // (TVF dispatch, connectivity, credentials) treats it like the 
built-in dialects —
+        // no new enum value or case additions needed. Note the type-keyed 
catalog map can
+        // hold one S3-family binding at a time, so one catalog cannot combine 
such a plugin
+        // with another S3-family storage. Non-S3 protocols stay UNKNOWN (they 
need the
+        // full scenario-B integration incl. BE support).
+        this.type = StorageRegistry.Provider.byName(providerKey)
+                .map(StorageRegistry.Provider::typeId)
+                .orElse(spi instanceof S3CompatibleFileSystemProperties
+                        ? StorageTypeId.S3 : StorageTypeId.UNKNOWN);
+        // Align fe-core S3Properties.initNormalizeAndCheckProps: the mode 
string is parsed with
+        // the fe-core enum (which rejects spellings the SPI tolerates) at 
construction time.
+        this.s3CredentialsMode = "S3".equals(providerKey)
+                ? 
AwsCredentialsProviderMode.fromString(feCoreS3CredentialsProviderType())
+                : null;
+        this.forceParsingByStandardUriValue = 
resolveForceParsingByStandardUri();
+        checkAzureOauth2OnlyForIcebergRest();
+    }
+
+    /**
+     * Binds the primary provider for the raw user properties, mirroring the 
legacy
+     * {@code StorageProperties.createPrimary}.
+     */
+    public static StorageAdapter of(Map<String, String> origProps) {
+        return new 
StorageAdapter(manager().bindPrimary(withHadoopConfigDir(origProps)), 
origProps);
+    }
+
+    /**
+     * Binds every matching provider, mirroring the legacy {@code 
StorageProperties.createAll}
+     * (multi-hit, OSS-HDFS/OSS exclusivity, default HDFS fallback at index 0).
+     */
+    public static List<StorageAdapter> ofAll(Map<String, String> origProps) {
+        List<FileSystemProperties> bindings = 
manager().bindAll(withHadoopConfigDir(origProps));
+        List<StorageAdapter> result = new ArrayList<>(bindings.size());
+        for (FileSystemProperties binding : bindings) {
+            result.add(new StorageAdapter(binding, origProps));
+        }
+        return result;
+    }
+
+    /**
+     * Binds one specific provider by name, bypassing routing entirely — the 
facade twin of the
+     * legacy per-dialect factories ({@code S3Properties.of}, {@code 
OSSProperties.of},
+     * {@code AzureProperties.of}, ...) that constructed and validated a fixed 
typed class no
+     * matter what routing would have chosen for the same map.
+     */
+    public static StorageAdapter ofProvider(String providerName, Map<String, 
String> origProps) {
+        Map<String, String> props = origProps == null ? new HashMap<>() : 
origProps;
+        for (FileSystemProvider<?> provider : manager().getProviders()) {
+            if (provider.name().equalsIgnoreCase(providerName)) {
+                return new 
StorageAdapter(provider.bind(withHadoopConfigDir(props)), props);
+            }
+        }
+        throw new StoragePropertiesException(
+                "Filesystem provider '" + providerName + "' is not available");
+    }
+
+    /**
+     * Returns whether the named provider's guess heuristics claim the raw 
properties — the
+     * facade twin of the legacy per-dialect {@code guessIsMe} statics (guess 
only, explicit
+     * {@code fs.<x>.support} flags deliberately NOT consulted, matching the 
legacy call sites).
+     */
+    public static boolean matchesProviderGuess(String providerName, 
Map<String, String> origProps) {
+        Map<String, String> props = origProps == null ? new HashMap<>() : 
origProps;
+        // Same probe view the bind registry uses: carries Config-derived 
guess context
+        // (e.g. the admin-extensible Azure host suffixes) into the provider.
+        Map<String, String> probeView = 
FileSystemPluginManager.withProbeContext(props);
+        for (FileSystemProvider<?> provider : manager().getProviders()) {
+            if (provider.name().equalsIgnoreCase(providerName)) {
+                return provider.supportsGuess(probeView);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Quirk preservation (converter parity): fe-filesystem resolves relative
+     * {@code hadoop.config.resources} entries against the injected {@code 
_HADOOP_CONFIG_DIR_}
+     * context key because it cannot see fe-core {@code 
Config.hadoop_config_dir}; the legacy
+     * conversion layer injected that key for every HDFS-family conversion. 
The marker is added
+     * only to the copy handed to {@code bind()} — {@link #getOrigProps()} and 
the
+     * Broker/Local/Http verbatim backend maps stay clean — and only when a
+     * {@code *config.resources} key is present (the only consumer of the 
marker).
+     */
+    private static Map<String, String> withHadoopConfigDir(Map<String, String> 
props) {
+        if (props == null || props.containsKey("_HADOOP_CONFIG_DIR_")
+                || StringUtils.isBlank(Config.hadoop_config_dir)) {
+            return props;
+        }
+        boolean hasConfigResources = false;
+        for (Map.Entry<String, String> entry : props.entrySet()) {
+            if (entry.getKey() != null && 
entry.getKey().endsWith("config.resources")
+                    && StringUtils.isNotBlank(entry.getValue())) {
+                hasConfigResources = true;
+                break;
+            }
+        }
+        if (!hasConfigResources) {
+            return props;
+        }
+        Map<String, String> copy = new HashMap<>(props);
+        copy.put("_HADOOP_CONFIG_DIR_", Config.hadoop_config_dir);
+        return copy;
+    }
+
+    /**
+     * Builds a BROKER adapter with an explicitly supplied broker name, 
mirroring the legacy
+     * {@code BrokerProperties.of(name, props)}: routing is bypassed (the 
props of a
+     * {@code WITH BROKER "name"} clause carry no {@code broker.name} key), 
the raw props are
+     * NOT mutated, and {@link #getBrokerName()} returns the supplied name.
+     */
+    public static StorageAdapter ofBroker(String brokerName, Map<String, 
String> origProps) {
+        Map<String, String> props = origProps == null ? new HashMap<>() : 
origProps;
+        for (FileSystemProvider<?> provider : manager().getProviders()) {
+            if ("BROKER".equalsIgnoreCase(provider.name())) {
+                return new StorageAdapter(provider.bind(props), props, 
brokerName);
+            }
+        }
+        throw new StoragePropertiesException("Broker filesystem provider is 
not available");
+    }
+
+    private static FileSystemPluginManager manager() {
+        FileSystemPluginManager managerFromStartup = pluginManager;
+        if (managerFromStartup != null) {
+            return managerFromStartup;
+        }
+        FileSystemPluginManager local = fallbackManager;
+        if (local == null) {
+            synchronized (StorageAdapter.class) {
+                local = fallbackManager;
+                if (local == null) {
+                    LOG.warn("StorageAdapter used before initPluginManager; 
falling back to"
+                            + " built-in providers loaded from the classpath. 
Expected in unit"
+                            + " tests only — in production this means the FE 
startup sequence"
+                            + " skipped filesystem plugin initialization and 
bindings may not"
+                            + " match the configured plugin directory.");
+                    local = new FileSystemPluginManager();
+                    local.loadBuiltins();
+                    fallbackManager = local;
+                }
+            }
+        }
+        return local;
+    }
+
+    /** The underlying SPI typed properties (for callers migrating deeper 
integrations). */
+    public FileSystemProperties getSpiProperties() {
+        return spi;
+    }
+
+    /** The raw user properties this adapter was bound from. */
+    public Map<String, String> getOrigProps() {
+        return origProps;
+    }
+
+    public StorageTypeId getType() {
+        return type;
+    }
+
+    /** Legacy getStorageName() (2.4-1 mapping: every S3-compatible dialect 
reports "S3"). */
+    public String getStorageName() {
+        // provider-declared family: the 2.4-1 "every S3 dialect reports S3" 
mapping lives in the SPI
+        return spi.storageFamilyName();
+    }
+
+    /** Legacy schemas() (drives ensureDisableCache): provider-declared legacy 
scheme set. */
+    public Set<String> schemas() {
+        // provider-declared legacy scheme set (ledger 2.4-6 note lives on 
legacyCacheSchemes)
+        return spi.legacyCacheSchemes();
+    }
+
+    /** Hadoop configuration equivalent of legacy getHadoopStorageConfig(); 
null for BROKER/HTTP. */
+    public Configuration getHadoopStorageConfig() {
+        if (!hadoopStorageConfigBuilt) {
+            synchronized (this) {
+                if (!hadoopStorageConfigBuilt) {
+                    hadoopStorageConfig = buildHadoopStorageConfig();
+                    hadoopStorageConfigBuilt = true;
+                }
+            }
+        }
+        return hadoopStorageConfig;
+    }
+
+    /** Legacy isKerberos() — meaningful for the HDFS family only. */
+    public boolean isKerberos() {
+        return 
spi.toHadoopProperties().map(HadoopStorageProperties::isKerberos).orElse(false);
+    }
+
+    /** Authenticator for HDFS-family doAs execution; DIRECT passthrough for 
everything else. */
+    public ExecutionAuthenticator getExecutionAuthenticator() {
+        return spi.toHadoopProperties()
+                .map(HadoopStorageProperties::getExecutionAuthenticator)
+                .orElse(ExecutionAuthenticator.DIRECT);
+    }
+
+    /**
+     * Legacy {@code HdfsProperties.isExplicitlyConfigured()}: {@code false} 
only for the
+     * default-HDFS fallback binding that {@code bindAll}/legacy {@code 
createAll} auto-prepends
+     * when nothing HDFS-like matched. A binding whose own provider matches 
the raw props
+     * (explicit {@code fs.x.support} flag or guess heuristics) is explicit; 
non-HDFS types were
+     * always explicit in fe-core.
+     */
+    public boolean isExplicitlyConfigured() {
+        if (type != StorageTypeId.HDFS) {
+            return true;
+        }
+        for (FileSystemProvider provider : manager().getProviders()) {
+            if (providerKey.equalsIgnoreCase(provider.name())) {
+                return provider.supportsExplicit(origProps) || 
provider.supportsGuess(origProps);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * fe-core S3Properties credentials-provider mode ({@code 
s3.credentials_provider_type}
+     * alias family, default DEFAULT). Non-null only for the S3 provider, 
mirroring the legacy
+     * class layout where only the generic S3 typed class carried the mode.
+     */
+    public AwsCredentialsProviderMode getAwsCredentialsProviderMode() {
+        return s3CredentialsMode;
+    }
+
+    /**
+     * fe-core-only AWS SDK credentials accessor, mirroring the legacy typed 
classes'
+     * {@code getAwsCredentialsProvider()} overrides exactly: static 
(optionally session)
+     * credentials first for every S3-compatible dialect; the S3 provider adds 
assume-role and
+     * the v1/v2 chain selection per {@code 
Config.aws_credentials_provider_version};
+     * OSS/GCS/COS/OBS fall back to anonymous credentials when both AK and SK 
are blank;
+     * everything else (Minio/Ozone/Azure and non-S3 types) returns {@code 
null}.
+     */
+    public AwsCredentialsProvider getAwsCredentialsProvider() {
+        if (!(spi instanceof S3CompatibleFileSystemProperties)) {
+            return null;
+        }
+        S3CompatibleFileSystemProperties s3 = 
(S3CompatibleFileSystemProperties) spi;
+        AwsCredentialsProvider staticProvider = 
staticAwsCredentialsProvider(s3);
+        if ("S3".equals(providerKey)) {
+            return s3AwsCredentialsProvider(s3, staticProvider);
+        }
+        if (staticProvider != null) {
+            return staticProvider;
+        }
+        switch (providerKey) {
+            case "OSS":
+            case "GCS":
+            case "COS":
+            case "OBS":
+                // Align fe-core OSS/GCS/COS/OBS Properties: anonymous access 
when unauthenticated.
+                if (StringUtils.isBlank(s3.getAccessKey()) && 
StringUtils.isBlank(s3.getSecretKey())) {
+                    return AnonymousCredentialsProvider.create();
+                }
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    /** Align fe-core AbstractS3CompatibleProperties.getAwsCredentialsProvider 
(static creds only). */
+    private static AwsCredentialsProvider 
staticAwsCredentialsProvider(S3CompatibleFileSystemProperties s3) {
+        if (StringUtils.isNotBlank(s3.getAccessKey()) && 
StringUtils.isNotBlank(s3.getSecretKey())) {
+            if (StringUtils.isEmpty(s3.getSessionToken())) {
+                return StaticCredentialsProvider.create(
+                        AwsBasicCredentials.create(s3.getAccessKey(), 
s3.getSecretKey()));
+            }
+            return 
StaticCredentialsProvider.create(AwsSessionCredentials.create(
+                    s3.getAccessKey(), s3.getSecretKey(), 
s3.getSessionToken()));
+        }
+        return null;
+    }
+
+    /** Align fe-core S3Properties.getAwsCredentialsProviderV1/V2 (assume-role 
+ chain selection). */
+    private AwsCredentialsProvider 
s3AwsCredentialsProvider(S3CompatibleFileSystemProperties s3,
+            AwsCredentialsProvider staticProvider) {
+        if (staticProvider != null) {
+            return staticProvider;
+        }
+        boolean v2 = 
Config.aws_credentials_provider_version.equalsIgnoreCase("v2");
+        if (StringUtils.isNotBlank(s3.getRoleArn())) {
+            StsClient stsClient = StsClient.builder()
+                    .region(Region.of(s3.getRegion()))
+                    .credentialsProvider(v2
+                            ? 
AwsCredentialsProviderFactory.createV2(s3CredentialsMode, false)
+                            : InstanceProfileCredentialsProvider.create())
+                    .build();
+            return StsAssumeRoleCredentialsProvider.builder()
+                    .stsClient(stsClient)
+                    .refreshRequest(builder -> {
+                        
builder.roleArn(s3.getRoleArn()).roleSessionName("aws-sdk-java-v2-fe");
+                        if (StringUtils.isNotBlank(s3.getExternalId())) {
+                            builder.externalId(s3.getExternalId());
+                        }
+                    }).build();
+        }
+        // For anonymous access (no credentials required) v1 uses the 
anonymous provider; v2
+        // delegates to the factory's default chain (which may include 
anonymous).
+        return v2 ? AwsCredentialsProviderFactory.createV2(s3CredentialsMode, 
true)
+                : AnonymousCredentialsProvider.create();
+    }
+
+    public String validateAndNormalizeUri(String uri) {
+        // Align fe-core AbstractS3CompatibleProperties/AzureProperties: the 
SPI S3/Azure typed
+        // props do not normalize URIs (compat schemes like cos:// must become 
s3:// before the
+        // path reaches BE or a concrete filesystem), so the facade owns the 
legacy logic.
+        if (spi instanceof S3CompatibleFileSystemProperties) {
+            return StorageUriUtils.validateAndNormalizeS3Uri(uri,
+                    ((S3CompatibleFileSystemProperties) spi).getUsePathStyle(),
+                    forceParsingByStandardUriValue,
+                    "OSS".equals(providerKey));
+        }
+        if ("AZURE".equals(providerKey)) {
+            return StorageUriUtils.validateAndNormalizeAzureUri(uri);
+        }
+        if (type == StorageTypeId.HDFS && StorageUriUtils.isJfsLocation(uri)) {
+            // Align fe-core: the legacy HDFS typed class accepted {hdfs, 
viewfs, jfs} while the
+            // HDFS plugin's scheme identity excludes jfs (it has its own 
plugin), so the facade
+            // owns the jfs leg of an HDFS binding.
+            return StorageUriUtils.validateAndNormalizeJfsUri(uri);
+        }
+        return spi.validateAndNormalizeUri(uri);
+    }
+
+    public String validateAndGetUri(Map<String, String> loadProps) {
+        // Align fe-core: the S3/Azure legacy classes return the RAW uri value 
(no normalization)
+        // and throw when the map is empty or has no uri key; the SPI default 
would silently
+        // return null instead.
+        if (spi instanceof S3CompatibleFileSystemProperties) {
+            return StorageUriUtils.validateAndGetS3Uri(loadProps);
+        }
+        if ("AZURE".equals(providerKey)) {
+            return StorageUriUtils.validateAndGetAzureUri(loadProps);
+        }
+        if (type == StorageTypeId.HDFS && loadProps != null) {
+            // jfs leg of the HDFS binding (see validateAndNormalizeUri 
above): a single
+            // case-insensitive `uri` entry with a jfs scheme is normalized by 
the facade.
+            String uriValue = loadProps.entrySet().stream()
+                    .filter(e -> "uri".equalsIgnoreCase(e.getKey()))
+                    .map(Map.Entry::getValue)
+                    .filter(StringUtils::isNotBlank)
+                    .findFirst()
+                    .orElse(null);
+            if (uriValue != null && uriValue.split(",").length == 1
+                    && StorageUriUtils.isJfsLocation(uriValue)) {
+                return StorageUriUtils.validateAndNormalizeJfsUri(uriValue);
+            }
+        }
+        return spi.validateAndGetUri(loadProps);
+    }
+
+    /** fe-core S3 family alias resolution for force_parsing_by_standard_uri 
(see map above). */
+    private String resolveForceParsingByStandardUri() {
+        String prefix = FORCE_PARSING_PREFIXES.get(providerKey);
+        String value = prefix == null ? null : getOrigPropIgnoreCase(prefix + 
".force_parsing_by_standard_uri");
+        if (StringUtils.isBlank(value)) {
+            value = getOrigPropIgnoreCase("force_parsing_by_standard_uri");
+        }
+        return StringUtils.isNotBlank(value) ? value : "false";
+    }
+
+    private String getOrigPropIgnoreCase(String key) {
+        for (Map.Entry<String, String> entry : origProps.entrySet()) {
+            if (key.equalsIgnoreCase(entry.getKey())) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    /** Legacy BrokerProperties.getBrokerName(): the explicit name from {@link 
#ofBroker} wins,
+     *  otherwise a case-insensitive {@code broker.name} lookup on the raw 
props. */
+    public String getBrokerName() {
+        if (brokerNameOverride != null) {
+            return brokerNameOverride;
+        }
+        return getOrigPropIgnoreCase("broker.name");
+    }
+
+    /** Legacy BrokerProperties.getBrokerParams(): {@code broker.}-prefixed 
keys, prefix stripped. */
+    public Map<String, String> getBrokerParams() {
+        Map<String, String> params = new HashMap<>();
+        for (Map.Entry<String, String> entry : origProps.entrySet()) {
+            if (entry.getKey().startsWith("broker.")) {
+                params.put(entry.getKey().substring("broker.".length()), 
entry.getValue());
+            }
+        }
+        return params;
+    }
+
+    /** Backend (BE) configuration map, key-for-key equal to the legacy typed 
classes. */
+    public Map<String, String> getBackendConfigProperties() {
+        if (backendConfigProperties == null) {
+            backendConfigProperties = computeBackendConfigProperties();
+        }
+        return backendConfigProperties;
+    }
+
+    private Map<String, String> computeBackendConfigProperties() {
+        switch (type) {
+            case BROKER:
+            case LOCAL:
+            case HTTP:
+                // Align fe-core: Broker/Local/Http return the raw user 
properties verbatim.
+                return origProps;
+            case AZURE:
+                return azureBackendConfigProperties();
+            default:
+                break;
+        }
+        Map<String, String> base = spi.toBackendProperties()
+                .orElseThrow(() -> new IllegalStateException(
+                        "Provider " + providerKey + " exposes no backend 
properties"))
+                .toMap();
+        if (spi instanceof S3CompatibleFileSystemProperties) {
+            return alignS3FamilyBackendMap((S3CompatibleFileSystemProperties) 
spi, base);
+        }
+        if ("JFS".equals(providerKey)) {
+            return alignJfsBackendMap(base);
+        }
+        return base;
+    }
+
+    /**
+     * Align fe-core, ledger 2.4-7: with OAuth2 the legacy AzureProperties 
dumps the ENTIRE
+     * Hadoop Configuration (hadoop defaults + fs.azure.* + user fs.* 
passthrough +
+     * disable-cache keys) as the backend map; shared-key uses the SPI's exact 
7-key map.
+     */
+    private Map<String, String> azureBackendConfigProperties() {
+        if (!isAzureOauth2()) {
+            return spi.toBackendProperties().orElseThrow().toMap();
+        }
+        Map<String, String> dump = new HashMap<>();
+        getHadoopStorageConfig().forEach(entry -> dump.put(entry.getKey(), 
entry.getValue()));
+        return dump;
+    }
+
+    /**
+     * Backend-map reconciliation for every S3-compatible provider 
(S3/OSS/OBS/COS/GCS/MinIO/Ozone).
+     */
+    private Map<String, String> 
alignS3FamilyBackendMap(S3CompatibleFileSystemProperties s3,
+            Map<String, String> base) {
+        Map<String, String> aligned = new HashMap<>(base);
+        // Align fe-core, ledger 2.4-5: fe-core never emits AWS_BUCKET / 
AWS_ROOT_PATH to BE.
+        aligned.remove("AWS_BUCKET");
+        aligned.remove("AWS_ROOT_PATH");
+        // Align fe-core, ledger 2.4-5: endpoint/region/AK/SK are emitted 
unconditionally —
+        // anonymous access sends empty-string AWS_ACCESS_KEY/AWS_SECRET_KEY, 
not absent keys.
+        aligned.put("AWS_ENDPOINT", s3.getEndpoint());
+        aligned.put("AWS_REGION", s3.getRegion());
+        aligned.put("AWS_ACCESS_KEY", s3.getAccessKey());
+        aligned.put("AWS_SECRET_KEY", s3.getSecretKey());
+        if ("S3".equals(providerKey)) {
+            // Align fe-core, ledger 2.4-3/5: S3Properties always emits the 
mode name (default
+            // DEFAULT, even with static AK/SK), parsed by the fe-core enum. 
AWS_ROLE_ARN /
+            // AWS_EXTERNAL_ID pass through unconditionally when set (the SPI 
already emits them).
+            aligned.put("AWS_CREDENTIALS_PROVIDER_TYPE", 
s3CredentialsMode.getMode());
+        } else {

Review Comment:
   [P1] Do not rewrite unlisted S3 roles as anonymous
   
   Every unlisted `S3CompatibleFileSystemProperties` binding is assigned 
`StorageTypeId.S3`, but any provider key other than the literal built-in `S3` 
enters this branch. For a typed third-party provider with a role ARN/external 
ID and no static keys, this removes both role fields and sends 
`AWS_CREDENTIALS_PROVIDER_TYPE=ANONYMOUS`; `getAwsCredentialsProvider()` 
likewise returns null for that role-bearing adapter. Thus even its standard 
`s3://` path fails in scan/sink and FE connectivity flows. Please apply the 
legacy non-S3 policy only to the known built-in dialects, and retain generic S3 
AssumeRole/default-chain behavior (or make credential policy explicit in the 
SPI), with a role-based unlisted-provider test.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to