This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master-catalog-spi-review-21
in repository https://gitbox.apache.org/repos/asf/doris.git

commit cef4f3fb88bea4cc6b33b6d8a89b7d782211cf72
Author: morningman <[email protected]>
AuthorDate: Tue Jul 28 14:28:27 2026 +0800

    [doc](catalog) add task space for fe-core datasource/property cleanup
    
    Records the investigation into whether fe-core
    datasource/property/{common,metastore} can be deleted or relocated to
    fe-connector / fe-filesystem, and the resulting plan.
    
    Verdicts differ per package and must not be treated as one job:
    
    - metastore/ (4 files, 333 lines) plus the orphaned ConnectionProperties
      is unreachable dead code. Its registry holds only TRINO_CONNECTOR,
      whose factory never parses anything; both doors into it are
      unreachable; nothing there is Gson-persisted; and its successor
      (fe-connector-metastore-api MetaStoreProperties + MetaStoreProviders +
      Connector.deriveStorageProperties) is already live. Nothing needs to
      be migrated -- only deleted.
    
    - common/ (2 files, 237 lines) serves internal storage (cold-storage
      StoragePolicy, cloud StorageVault, TVF/backup/export), not external
      datasources. No fe-connector or fe-filesystem module imports it, every
      relocation target is dependency- or classloader-illegal, and reusing
      the fe-filesystem-s3-base twin is a behavior change rather than a
      refactor. Only its provably dead half is removed.
    
    The space follows the hive-catalog-shade-removal layout and adds an
    open-decisions file, since one decision (fail-loud vs fail-silent on the
    null-supplier branch) gates the main deletion.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 plan-doc/fecore-property-cleanup/HANDOFF.md        |  92 +++++++
 plan-doc/fecore-property-cleanup/README.md         |  76 ++++++
 plan-doc/fecore-property-cleanup/design.md         | 278 +++++++++++++++++++++
 plan-doc/fecore-property-cleanup/open-decisions.md | 115 +++++++++
 plan-doc/fecore-property-cleanup/progress.md       |  90 +++++++
 plan-doc/fecore-property-cleanup/tasklist.md       | 177 +++++++++++++
 6 files changed, 828 insertions(+)

diff --git a/plan-doc/fecore-property-cleanup/HANDOFF.md 
b/plan-doc/fecore-property-cleanup/HANDOFF.md
new file mode 100644
index 00000000000..8e40b1db753
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/HANDOFF.md
@@ -0,0 +1,92 @@
+# 🤝 Session Handoff — 清理 fe-core `datasource/property/{common,metastore}`
+
+> **滚动文档**:每次 session 结束**覆盖式更新**,**只保留下一个 session 必须的上下文**。
+> 完成的明细**不落这里**(在 `git log` + [`progress.md`](./progress.md) 里)。
+> 空间索引 [`README.md`](./README.md) · 设计 [`design.md`](./design.md) ·
+> 清单 [`tasklist.md`](./tasklist.md) · 待拍板 
[`open-decisions.md`](./open-decisions.md)
+
+---
+
+# 🆕 下一个 session 第一件事 = **拿 OD-1 的拍板**(`open-decisions.md`)
+
+## 状态:**调研已完成,代码一行未动。**
+
+**基线 HEAD** = `3468d905eb3`(分支 `catalog-spi-review-21`,2026-07-28)。
+本空间是**新建**的,尚无任何本任务的 commit。
+
+---
+
+## 📍 你现在需要知道的三件事
+
+### 1️⃣ 两个包的答案不一样,别当成一件事做
+
+| 包 | 裁决 |
+|---|---|
+| `metastore/`(4 文件 333 行)+ `ConnectionProperties.java`(140 行) | **整体删除** —— 
运行期不可达,接班人 `fe-connector-metastore-api` 早已上线,**没有东西需要搬** |
+| `common/`(2 文件 237 行) | **留在 fe-core** —— 它服务的是**内部存储**(冷存 StoragePolicy / 
云上 StorageVault),不是外部数据源;只砍掉死的一半 |
+
+### 2️⃣ 🔴 一个必须记住的「别再犯」
+
+调研初判说 `common/` 和 `fe-filesystem-s3-base` 的
+`S3CredentialsProviderType`/`S3CredentialsProviderFactory` 是重复造轮子、可以直接替换 ——
+**这个判断被对抗验证两轮推翻了**。两份实现有**两条活的行为差异**(`design.md` §3.3):
+
+1. 发给 hadoop 的凭证串会**多出** `ProfileCredentialsProvider`
+2. 模式串接受面会**放宽**(空串 / `ENVIRONMENT` / `WEB_IDENTITY_TOKEN_FILE` 从抛异常变成接受)
+
+而**全仓没有任何测试钉住那个串** ⇒ 换掉会**绿着上线一个回归**。
+**下次看到「这两个类长得一样,合并掉吧」的念头,先来读 `design.md` §3.3。**
+
+### 3️⃣ ⛔ 卡在哪:OD-1 没拍板
+
+`open-decisions.md` **OD-1**:删掉 metastore 后,`resolveDerivedStorageDefaults()` 的
+null-supplier 分支要 **fail-loud(`throw`,推荐)** 还是 **fail-silent(`return 
emptyMap()`)**?
+
+这条**直接决定 FPC-03 的代码怎么写**,不定不能开工。
+(OD-2 是「FPC-02 做不做」,不阻塞任何东西,可以随时定。)
+
+---
+
+## ▶️ 建议的执行顺序
+
+```
+1. 拿 OD-1 拍板                      ← 就是这一步,先做
+2. FPC-02(删 AWS 死构造臂,~146 行)  ← 可选、解耦,先做也行、跳过也行
+3. FPC-03(主删除:5 文件 473 行 + CatalogProperty 瘦身 ~45 行)  ← 依赖 OD-1
+4. FPC-04(可选清扫,另起提交)
+```
+
+---
+
+## ⚠️ 开工前必读的三条纪律(本仓库已知踩坑)
+
+1. **删除类改动不能只信增量编译** —— `fe-core/target/classes` 里确实躺着无源文件的陈旧 `.class`。
+   每步先 `rm -rf fe-core/target/{classes,test-classes}`。
+2. **全反应堆必须含测试源**(禁 `-Dmaven.test.skip=true`),且必须 `-Dcheckstyle.skip=true`
+   (否则 checkstyle 扫 generated-sources 退化成平方级,构建卡死 60+ 分钟);
+   checkstyle 改为**只对改动模块**单独跑 `checkstyle:check`。
+3. **`fe-connector-api` 的录制基线要显式跑** ——
+   `mvn -pl fe-connector/fe-connector-api test`。全反应堆 `test-compile` **不跑 
surefire**,
+   这是本分支已经红过好几批没人发现的盲区。
+   本任务**预期不需要刷基线**(该模块不依赖 fe-core),**红了就停手,别顺手刷。**
+
+---
+
+## 🔎 尚未验证(如实声明,别当成已完成)
+
+- **没跑过任何 maven 构建** —— `tasklist.md` 里的验证命令是方子,不是结果
+- **没查 apache/doris master** 是否有 `StorageAdapter.getAwsCredentialsProvider()` 
的调用者
+  (FPC-02 的 rebase 冲突风险是陈述不是实测)
+- **没跑 e2e**(需要集群)
+- `ExternalCatalog.buildHadoopConfiguration(Map)` 的调用者没枚举 ⇒ FPC-04 明确排除它
+
+---
+
+## 📎 参考:完整英文调研报告
+
+29k 字的原始报告(逐条 `file:line` 证据 + 三份独立设计的分歧点 + 六项对抗验证的两轮判决)
+在调研 session 的 scratchpad:
+`/tmp/claude-1000/-mnt-disk1-yy-git-wt-catalog-spi/6983e5ef-36cf-4f14-a048-139ffc1c1b51/scratchpad/property-common-metastore-report.md`
+
+⚠️ **scratchpad 是 session 级的,可能已经不在了。** 本空间的 `design.md` 已经把其中**结论性、
+需要长期保留**的部分中文化落盘 —— 以 `design.md` 为准,那份报告只是溯源用。
diff --git a/plan-doc/fecore-property-cleanup/README.md 
b/plan-doc/fecore-property-cleanup/README.md
new file mode 100644
index 00000000000..81dba34156c
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/README.md
@@ -0,0 +1,76 @@
+# 📦 任务空间 — 清理 fe-core `datasource/property/{common,metastore}`
+
+> **独立任务空间**,与 catalog-spi 主线(`plan-doc/HANDOFF.md`)并行但**不混流**。
+> 目标:按架构目标「**fe-core 不持有任何属性解析**」,处置 fe-core
+> `org.apache.doris.datasource.property` 下的 `common/`(AWS 凭证模式)与 
`metastore/`(元存储属性)两个包。
+
+---
+
+## 🚩 一句话结论(2026-07-28 基线 `3468d905eb3`,8 路侦察 + 3 路独立设计 + 6 项对抗验证)
+
+**两个包的答案不一样,别当成一件事做。**
+
+| 包 | 裁决 | 一句话理由 |
+|---|---|---|
+| `metastore/`(4 文件 333 行) | **整体删除**(连带孤儿 `ConnectionProperties.java`) | 
运行期**不可达**;接班人 `fe-connector-metastore-api` 早已上线跑着 → **没有任何东西需要搬** |
+| `common/`(2 文件 237 行) | **留在 fe-core,砍掉死的一半** | 它服务的是**内部存储**(冷存 
StoragePolicy / 云上 StorageVault),**不是外部数据源**;三个候选目的地全部堵死;「复用 fe-filesystem 
现成那份」是**行为变更**不是重构 |
+
+**⚠️ 最容易踩的坑**:`common/` 看起来和 `fe-filesystem-s3-base` 的
+`S3CredentialsProviderType`/`S3CredentialsProviderFactory` 是重复造轮子 —— 
**调研初判就是这么错的**。
+两份实现有**两条活的行为差异**(见 `design.md` §3.3),且全仓**没有任何测试**钉住发出的凭证串,
+换掉会绿着上线一个回归。
+
+---
+
+## 📂 本空间文件
+
+| 文件 | 用途 | 更新方式 |
+|---|---|---|
+| [`design.md`](./design.md) | **设计文档** —— 两个包的判据、证据链、被否方案、风险 | 稳定文档,改动需在 
progress 留痕 |
+| [`tasklist.md`](./tasklist.md) | **Task list** —— 唯一进度清单,`FPC-NN` 勾选 | 
每完成一项随 commit 勾 `[x]` |
+| [`open-decisions.md`](./open-decisions.md) | **待拍板** —— 动手前需要用户定的事 | 拍板后就地标 
✅ 并写明结论 |
+| [`HANDOFF.md`](./HANDOFF.md) | **交接文档** —— 只写「下一个 session 第一件事做什么」 | 每 
session 结束**覆盖式**更新 |
+| [`progress.md`](./progress.md) | **进度记录** —— append-only 日志(日期 / commit / 结论 
/ 踩坑) | 只追加,不覆盖 |
+
+---
+
+## ▶️ 新 session 开场流程(必须遵守)
+
+```
+1. Read plan-doc/fecore-property-cleanup/HANDOFF.md      ← 上次留言 + 下一步
+2. Read plan-doc/fecore-property-cleanup/tasklist.md     ← 勾到哪了
+3. Read plan-doc/fecore-property-cleanup/open-decisions.md ← 有没有还没拍板的
+4. 需要背景/为什么时才 Read design.md(别默认全读,它是稳定参考不是状态)
+5. 用一句话向用户复述:"上次做完了 X,下一步是 FPC-NN,对吗?"
+6. 等用户确认后开始
+```
+
+**⚠️ 行号信 HEAD 不信文档** —— 本空间所有 `file:line` 是 **2026-07-28 / `3468d905eb3`** 基线,
+代码动了就以 `grep` 为准。
+
+---
+
+## 🔗 与其它空间的关系
+
+- **主线** = `plan-doc/HANDOFF.md`(catalog-spi 迁移)。本任务是主线「fe-core 去属性解析」的收尾一环。
+- **`../metastore-storage-refactor/`(已 CLOSED)** = 本任务的**前置**:正是那条子线**生产**出了
+  `fe-connector-metastore-api` / 
`fe-connector-metastore-spi`(`MetaStoreProviders.bind` + 5 个 provider)。
+  本任务只是把它留在 fe-core 的**旧壳**扫掉。⛔ 那个目录是历史留存,别去读它的规划文档。
+- **继承主线两条铁律**:**fe-core 只出不进**(铁律 A) + **禁 deletion-scaffolding 式搬迁**(铁律 B)。
+  本任务全程 fe-core **只减不增**,两条天然满足。
+- 协作规范沿用 [`../AGENT-PLAYBOOK.md`](../AGENT-PLAYBOOK.md)。
+
+---
+
+## 📌 范围边界(误判比漏判贵)
+
+**在范围内**:
+- `fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/`(4 
文件)
+- 
`fe/fe-core/src/main/java/org/apache/doris/datasource/property/ConnectionProperties.java`(删
 metastore 后成孤儿)
+- 
`fe/fe-core/src/main/java/org/apache/doris/datasource/property/common/`(只砍死代码,**不搬迁**)
+- `CatalogProperty.java` 的连带瘦身
+
+**明确不在范围**(理由见 `design.md` §6):
+- `datasource/property/constants/`、`datasource/property/fileformat/` —— 
**压根不是外部数据源逻辑**
+- `StorageAdapter.checkAzureOauth2OnlyForIcebergRest()` —— 是真的架构违规,但需**单独一刀 + 
e2e**
+- 把 `common/` 搬去任何模块 —— 三条独立理由否决,见 `design.md` §3
diff --git a/plan-doc/fecore-property-cleanup/design.md 
b/plan-doc/fecore-property-cleanup/design.md
new file mode 100644
index 00000000000..d0b7b10bd7a
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/design.md
@@ -0,0 +1,278 @@
+# 设计文档 — 清理 fe-core `datasource/property/{common,metastore}`
+
+> **稳定参考文档**,不是状态。状态看 [`tasklist.md`](./tasklist.md) / 
[`HANDOFF.md`](./HANDOFF.md)。
+> **基线**:2026-07-28 / `3468d905eb3` / 分支 `catalog-spi-review-21`。
+> **⚠️ 行号信 HEAD 不信文档。**
+>
+> **调研方法**:8 路并行侦察(172 条 finding)→ 3 路独立设计(minimal / architectural / risk 三种视角)
+> → 6 项对抗验证(**2 项被推翻**,被推翻的用修正版)→ 综合。承重结论由本文作者逐条复核代码。
+
+---
+
+## 1. 现状盘点
+
+`fe/fe-core/src/main/java/org/apache/doris/datasource/property/` 共 21 个文件 2105 
行,四个子域:
+
+| 子域 | 文件数 / 行数 | 是不是「外部数据源」 | 本任务处置 |
+|---|---|---|---|
+| `metastore/` | 4 / 333 | ✅ 是 | **删** |
+| `ConnectionProperties.java` | 1 / 140 | ✅ 是(`metastore/` 的基类) | **删**(孤儿) |
+| `common/` | 2 / 237 | ❌ **不是**(内部存储) | **留**,砍死代码 |
+| `constants/` + `fileformat/` | 14 / 1395 | ❌ 不是 | 不在范围(§6) |
+
+---
+
+## 2. `metastore/` — 判死证据链
+
+### 2.1 结构:已经是空壳
+
+```
+metastore/MetastoreProperties.java              191 行  ← 注册表 + Type 枚举 + 基类
+metastore/MetastorePropertiesFactory.java        36 行  ← 接口
+metastore/AbstractMetastorePropertiesFactory.java 74 行  ← 子类型分发骨架
+metastore/TrinoConnectorPropertiesFactory.java   32 行  ← 唯一注册的工厂
+```
+
+- `MetastoreProperties.java:86-93` 的静态注册表**只剩 `Type.TRINO_CONNECTOR`**。
+  hms / iceberg / paimon 三家的工厂在 "Design S7" 时被**主动摘掉**,注释明说
+  「Type 枚举值保留(好让走岔的 `create()` 响亮报错),但工厂**故意不注册**」。
+- 而这唯一的 `TrinoConnectorPropertiesFactory.java:28-31` override 了 `create()`,直接
+  `return new MetastoreProperties(Type.TRINO_CONNECTOR, props)` —— **连
+  `initNormalizeAndCheckProps()` 都不调**。⇒ 零解析。
+- 连带后果:`AbstractMetastorePropertiesFactory.createInternal`(`:58`)**零调用者**,
+  `ConnectionProperties.initNormalizeAndCheckProps()` 的 `@ConnectorProperty` 
反射绑定
+  **永不执行**,`getDerivedStorageProperties()`(`:157-159`)是写死的 `emptyMap()` 
且**零子类**,
+  `getExecutionAuthenticator` / `asLegacyAuthenticator` / 
`StorageAuthenticatorBridge`(`:168-190`)
+  **零调用者**。
+
+**⚠️ 别被 `Type.TRINO_CONNECTOR` 骗了**:`type=trino-connector` 的目录是走
+**fe-connector-trino 插件**的,根本不经这个工厂。
+
+### 2.2 可达性:两道门都关死
+
+全仓唯一 import 者 = `CatalogProperty.java:21`。它开了两道门:
+
+**门一(`checkMetaStoreAndStorageProperties`,`CatalogProperty.java:307-321`)—— 
死。**
+全仓仅有它自己的声明,**零调用者**(含 `regression-test/` groovy)。⇒ `:310` 的
+`MetastoreProperties.create()` 不可达。
+
+**门二(`resolveDerivedStorageDefaults`,`CatalogProperty.java:264-272`)—— 被门挡死。**
+```java
+Supplier<Map<String,String>> pluginSupplier = 
pluginDerivedStorageDefaultsSupplier;
+if (pluginSupplier != null) { return pluginSupplier.get(); }   // ← 插件目录永远走这条
+MetastoreProperties msp = getMetastoreProperties();            // ← 只有 
supplier==null 才走
+```
+- `PluginDrivenExternalCatalog.java:177` **无条件**安装这个 supplier(Design S8)。
+- fe-core main 源里 `ExternalCatalog` 的具体子类**只剩 3 
个**:`PluginDrivenExternalCatalog`、
+  `RemoteDorisExternalCatalog`、`TestExternalCatalog`;后两个**完全不碰 
storage/hadoop/metastore**。
+
+**门二的时序窗口(曾被判 HIGH 风险,复核后判为不可达)**:
+`PluginDrivenExternalCatalog.java:150` 构造连接器时,`DefaultConnectorContext` 已经接上了
+`catalogProperty` 的 storage supplier(`:206-208`),而派生 supplier 要到 `:177` 才安装。
+⇒ 理论上存在「supplier 还是 null 就访问 storage」的窗口。
+**但实测不可达**:
+- `PaimonConnector` 构造函数(`:151-166`)只传方法引用 `this::pluginAuthenticator`(**惰性 
memoize**,
+  见 `:174-186`),不碰 storage;
+- `IcebergConnector` 构造函数(`:215+`)同样只传 `this::pluginAuthenticator`;
+- `HiveConnector` / `HudiConnector` 构造函数中**无任何** `storage()` / 
`getStorageProperties` 调用;
+- `IcebergConnectorProvider.validateProperties`(`:78-82`)/ 
`PaimonConnectorProvider`(`:94-98`)
+  显式传 `Collections.emptyMap()`,javadoc 明写「验证不需要 storage」。
+
+⇒ **窗口存在于代码形状上,但今天没有任何路径走进去。** 它只影响「删除后是 fail-loud 还是
+fail-silent」这个选择,见 [`open-decisions.md`](./open-decisions.md) OD-1。
+
+### 2.3 持久化:无坑
+
+这条必须查,因为本仓库有前科(`EsTable`/`EsResource`:删掉 Gson 注册的持久化类 →
+`RuntimeTypeAdapterFactory` 对未注册 clazz 硬抛 → **老镜像 FE 起不来**)。
+
+- `property/` 树下**无任何** `@SerializedName` / Gson / `Serializable` / `Writable`;
+- `CatalogProperty` 只持久化 `resource`(`:56`)和 `properties`(`:59`);
+  `metastoreProperties`(`:94`)**无注解**,被 
`GsonUtilsBase.HiddenAnnotationExclusionStrategy` 跳过;
+- 不是任何 `RuntimeTypeAdapterFactory` 的基类或子类型;
+- `gensrc/thrift`、`gensrc/proto`、所有 `META-INF/services`、所有 
`.groovy`/`.out`/`.sh`、
+  两个 build gate 里**全无踪迹**。
+
+⚠️ 注意区分:`InitCatalogLog.Type` / `InitDatabaseLog.Type` 里的 `TRINO_CONNECTOR` 
是**另一个**
+枚举(那个是持久化的,**别碰**);`MetastoreProperties.Type` 只在运行期计算,不持久化。
+
+### 2.4 接班人已上线 ⇒ 没有东西需要搬
+
+| fe-core(要删的) | 连接器侧接班人(已在跑) |
+|---|---|
+| `MetastoreProperties` | 
`org.apache.doris.connector.metastore.MetaStoreProperties`(fe-connector-metastore-api)
 |
+| `MetastorePropertiesFactory` + `AbstractMetastorePropertiesFactory` 注册表 | 
`MetaStoreProvider` / `MetaStoreProviders.bind(…)` / 
`bindForType(…)`(fe-connector-metastore-spi) |
+| `getDerivedStorageProperties()` | 
`Connector.deriveStorageProperties(Map)`(fe-connector-api),实现见 
`IcebergConnector.java:1161-1198` |
+| `ConnectionProperties` 的 `@ConnectorProperty` 反射绑定 | 
`AbstractMetaStoreProperties` + 
`MetaStoreParseUtils`(fe-connector-metastore-spi) |
+
+消费点:`PaimonConnectorProvider.java:96`、`IcebergConnectorProvider.java:80`、
+`HiveConnector.java:720`、`HudiConnector.java:296`。
+
+### 2.5 `ConnectionProperties` 顺带成孤儿
+
+- 全仓唯一子类就是 `MetastoreProperties`(`:46`)。
+- `StorageAdapter.java:856` 那处引用 —— **是 javadoc 散文,不是代码**:
+  `* {@code ConnectionProperties.equals}: logically identical configurations 
must share one`,
+  位于 `StorageAdapter` 自己 `equals()` 上方的注释块里。
+  ⇒ **`StorageAdapter` 不需要改就能编过**;改它只是为了不留悬空引用(cosmetic)。
+
+---
+
+## 3. `common/` — 判「留」证据链
+
+### 3.1 它不是外部数据源代码
+
+| 消费者 | 服务的功能 |
+|---|---|
+| `StorageAdapter.java:21-22` | 内部存储适配 |
+| `S3ThriftAdapter.java:20` | 发给 BE 的 S3 thrift 参数 |
+| `CloudObjectStoreAdapter.java:23` | 云上 
`StorageVault`(`StorageVaultMgr.java:150`) |
+| `AzureGuessRoutingParityTest.java:21`(测试) | 上述的对齐测试 |
+
+再往上:冷存 `StoragePolicy`(`PushStoragePolicyTask.java:91`)、TVF / backup / export。
+
+**零个 `fe-connector-*` 模块、零个 `fe-filesystem-*` 模块 import 它。** 它不在这次迁移的射程内。
+
+### 3.2 三个候选目的地全部堵死
+
+| 目的地 | 为什么不行 |
+|---|---|
+| `fe-filesystem-s3-base` | ① fe-core 对它**任何 scope 的依赖都没有**(`grep s3-base 
fe/fe-core/pom.xml` 退出码 1);② 它是 IMPL 层,`fe/fe-filesystem/README.md:47-63` 禁止 
fe-core 依赖;③ **最要命**:`FileSystemPluginManager.java:88-90` 把 
`org.apache.doris.filesystem.` 设为 **parent-first** → 把 s3-base jar 放进 `fe/lib` 
会**静默遮蔽** s3/gcs/minio/ozone 各插件自带的同名类(本仓库已知的 split-brain 类坑) |
+| `fe-filesystem-api` | 按约定只放 JDK 类型;而工厂 import 了 
`software.amazon.awssdk.auth.credentials.*`(`AwsCredentialsProviderFactory.java:25-32`)
 |
+| `fe-foundation` | 不带 AWS SDK;且 `org.apache.doris.foundation.` **不在** 
`ConnectorPluginManager.CONNECTOR_PARENT_FIRST_PREFIXES` 里(`:73-74`),而各连接器 zip 
都打包 `fe-foundation.jar` → 潜在 duplicate-Class LinkageError |
+
+### 3.3 ⚠️ 「复用 fe-filesystem 现成那份」是行为变更,不是重构
+
+**这是本次调研初判出错、被对抗验证两轮推翻的地方,务必记住。**
+
+仓库里现在有**四份** AWS 凭证模式实现:
+
+| | 位置 |
+|---|---|
+| A | fe-core 
`property/common/{AwsCredentialsProviderMode,AwsCredentialsProviderFactory}` |
+| B | `fe-filesystem-s3-base` 
`{S3CredentialsProviderType,S3CredentialsProviderFactory}` |
+| C | `fe-connector-iceberg` `AwsCredentialsProviderModes`(注释自称 
"self-contained twin",因连接器不能 import fe-core) |
+| D | `fe-connector-metastore-iceberg` `IcebergRestMetaStoreProperties` 
内联的模式检查 |
+
+**A → B 替换会带来两条活的行为差异**:
+
+1. **发给 hadoop 的凭证 provider 串会多一项**
+   `,software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider`
+   (B 的 `S3CredentialsProviderFactory.java:115` 有,A 的 `:116-129` 没有)。
+   活链路:`StorageAdapter.java:101-105`(`S3_CREDENTIAL_KEYS`)→ `:678-694` 跳过 SPI 
的值并重新推导
+   → `:730` / `:739` 写 `fs.s3a.assumed.role.credentials.provider` / 
`fs.s3a.aws.credentials.provider`
+   → `CatalogProperty.java:383` 消费。
+
+2. **模式串接受面放宽**
+   
B(`S3CredentialsProviderType.java:45,:51,:56`)接受空串、`ENVIRONMENT`、`WEB_IDENTITY_TOKEN_FILE`;
+   A(`AwsCredentialsProviderMode.java:48,:69-72`)对这些**抛异常**。
+   而 `StorageAdapter.java:169-170` 的注释明说这个严格是**故意的**。报错文案也不同。
+
+**而 `fe/` 和 `regression-test/` 里没有任何测试钉住发出的那个串** ⇒ 这个回归会**绿着上线**。
+
+**已确认是死代码的差异**(对抗第一轮夸大、第二轮修正):
+- A 与 B 的 `DEFAULT` 链差异,在 fe-core 侧**只能**经 
`StorageAdapter.getAwsCredentialsProvider()`
+  (`:391`)到达,而该方法**零调用者**(全仓只有它自己的声明和 javadoc)。
+  ⇒ 「匿名访问突然开始签名」这个吓人的场景**今天不可能发生**。
+- `AwsCredentialsProviderFactory.getV2ClassName(mode)`(单参,`:141-162`)同样零调用者。
+
+**真正对齐的部分**:只有发给 BE 的 `AWS_CREDENTIALS_PROVIDER_TYPE` 
值(`StorageAdapter.java:614,:623`)
+—— 两个枚举声明了相同的 7 个常量且 `getMode() == name()`。
+
+### 3.4 能砍的:约 146 行纯死代码
+
+- `StorageAdapter.getAwsCredentialsProvider()`(`:383-416` 含 javadoc)+
+  `staticAwsCredentialsProvider(...)`(`:418-429`)+ 
`s3AwsCredentialsProvider(...)`(`:431-458`)
+- `AwsCredentialsProviderFactory.createV2(mode,boolean)`(`:46-68`)+
+  `createDefaultV2(boolean)`(`:80-99`)+ 单参 `getV2ClassName(mode)`(`:136-162`)
+
+**必须保留**:`StorageAdapter.getAwsCredentialsProviderMode()`(`:379-381`)和 
`s3CredentialsMode` 字段
+(被 `AzureGuessRoutingParityTest` 钉住,且喂 `:614` 那个**活的** BE 值);
+`AwsCredentialsProviderFactory.getV2ClassName(mode, boolean)`(`:101-134`)和两个 
env 探针(`:70-78`);
+`StorageAdapter` 的 `InstanceProfileCredentialsProvider` import(`:42`,`:731` 
在用)。
+
+---
+
+## 4. 🔴 checkstyle 是硬门禁(两份候选设计栽在这)
+
+`fe/pom.xml:114` 在**父 `<build><plugins>`** 里声明 `maven-checkstyle-plugin`,
+`fe/pom.xml:177-183` 把 `check` 绑到**每个模块的 `validate` 阶段**;
+`fe/check/checkstyle/checkstyle.xml:27` 设 `severity=error`、`:167` 开 
`UnusedImports`;
+`suppressions.xml` 对这两个路径都没有豁免。
+
+⇒ **漏删一个 import,不带 flag 的 `mvn test` 会在跑任何测试之前就中止。**
+
+三份候选设计里**有两份的 import 清单是错的**。修正后的精确清单见 `tasklist.md` 各任务。
+
+---
+
+## 5. 风险登记
+
+| 级别 | 风险 | 缓解 |
+|---|---|---|
+| 🟠 中 | 删除后,理论上的 null-supplier 窗口从 **fail-loud** 变 **fail-silent**(返回 
`emptyMap` → 丢掉 iceberg `warehouse→fs.defaultFS` 桥接 → 而且因为 setter 故意不重置缓存,错误的 
`StorageBindings` 会被**永久缓存**) | 窗口今天不可达(§2.2)。处置方式见 
[`open-decisions.md`](./open-decisions.md) **OD-1**(推荐:null 分支 
`throw`,精确保留今天行为) |
+| 🟠 中 | 删除类改动**不能只信增量编译**——`fe-core/target/classes` 里确实存在无源文件的陈旧 `.class` | 每步 
`rm -rf fe-core/target/{classes,test-classes}` + 全反应堆 `clean 
test-compile`(**含测试源,禁 `-Dmaven.test.skip=true`**)+ 全仓 `grep -rIn`(不是符号 grep) |
+| 🟠 中 | checkstyle `UnusedImports` 门禁(§4) | 每步把 `checkstyle:check` 当**阻塞项**跑 |
+| 🟠 中 | `fe-connector-api` 的录制基线(`ConnectorMetadataSurfaceTest` ↔ 
`connector-metadata-methods.txt`)是本分支已知盲区——全反应堆 test-compile **不跑 surefire** | 
FPC-03 的验收**显式**跑 `-pl fe-connector/fe-connector-api test`。**预期不需要刷基线**(该模块不依赖 
fe-core,72 行基线只用 `connector.api.*`/`java.*` 类型)——**一旦红了就停手** |
+| 🟢 低 | FPC-02 删的 `StorageAdapter.getAwsCredentialsProvider()` 是上游 
`f499c78c67c`(#66004)整体带进来的;若 apache/doris master 有或将有调用者,下次 rebase 会 
modify/delete 冲突 | FPC-02 可整项丢弃,不影响其它任务。落地前 grep 一次上游 master |
+
+---
+
+## 6. 兄弟目录:明确不在范围
+
+### `fileformat/`(11 文件 ~1318 行)— 命名不当,不是架构违规
+解析的是 LOAD(broker/routine/mysql)、`SELECT … INTO OUTFILE`、`COPY INTO`、文件 TVF 
的读写选项,
+直接吐 fe-thrift 类型(`FileFormatProperties.java:21-25` import 
`TFileAttributes`/`TFileCompressType`/
+`TFileFormatType`/`TResultFileSinkOptions`)。24 个消费者**全在 fe-core**,零连接器 / 零 
fe-filesystem 引用。
+**它挂在 `datasource.property` 下只是名字取错了**,改名是 24 文件的纯 churn,无架构收益。
+
+### `constants/`(3 文件)— 不是数据源属性
+`AIProperties` 是 AI/LLM 模型资源;`RemoteDorisProperties` 是 Doris-to-Doris 
目录的**纯键名常量、零解析**
+(import 者只有 `catalog/AIResource.java:22`、`AIResourceTest.java:24`、
+`datasource/doris/RemoteDorisExternalCatalog.java:26`)。
+🔎 **顺带发现一个可独立清理项**(另开 ticket,不并入本任务):
+`constants/BaseProperties.getCloudCredential(...)` **零调用者**,其唯一作用是当 
`AIProperties.java:28` 的空父类。
+
+### `StorageAdapter.checkAzureOauth2OnlyForIcebergRest()`(`:822-843`)— 
真违规,但要单独一刀
+它在 **storage 路径**上读 **metastore 命名空间**的键(`type` / `iceberg.catalog.type`),
+是货真价实的 ARCH-GOAL 违规。但它是上游 #66004 的代码,带着**刻意的大小写敏感怪癖**,
+需要自己的一刀 + e2e。**在此记下,免得丢。**
+
+---
+
+## 7. 被否方案(存档,免得下次重走)
+
+1. **把 `common/` 搬去 `fe-filesystem-s3-base` / `fe-filesystem-api` / 
`fe-foundation`** — §3.2 三条独立理由。
+2. **把 fe-core 三个 adapter 改指向 `fe-filesystem-s3-base` 的现成实现,然后删 `common/`**
+   (architectural 视角的完整 S3–S5 轨道:把 `S3CredentialsProviderType` 上提到 
`fe-filesystem-api`、
+   调和 `hadoopClassName`、再删 `common/`)。
+   —— 它**在架构上是对的终局,本文不称其为错**,但:① 需要用户对
+   `Config.aws_credentials_provider_version` 的 v1 
分支(`Config.java:3740`)和「最终发哪条 DEFAULT 链」
+   两个问题拍板;② 会改 FE→hadoop 的线上串和持久化的 `AWS_CREDENTIALS_PROVIDER_TYPE` 别名族;
+   ③ **对 catalog-SPI 迁移零收益**(消费者是内部存储代码)。**另开 ticket 跟踪。**
+3. **给 fe-core 加新 SPI**(如在 `S3CompatibleFileSystemProperties` 上加
+   `hadoopCredentialsProviderClassName(boolean)`)—— 为了让 fe-core 
**继续留在**凭证生意里而发明的
+   additive SPI,违背方向;且六个 S3 
方言无从作答(`AbstractDelegatingS3Properties.java:216-224`
+   把 role/external-id 写死为空)。
+4. **让 fe-core 依赖 `fe-connector-metastore-api`** —— FPC-03 之后 fe-core 不再持有任何 
metastore 代码,
+   这个依赖零用户,还会把 `fe-kerberos` 拖上 fe-core 的 classpath。
+   🔎 顺带记一笔:该模块 `pom.xml:64` 的注释「This module is compiled into fe-core」**是过时的假话**
+   —— 没有任何 pom 声明它,它是打进 paimon/iceberg/hms/hudi 各插件 zip 的。
+5. **收敛第三份 AWS twin**(`fe-connector-iceberg/AwsCredentialsProviderModes`)—— 
它确实是**第三个变体**
+   (未知模式回退 DEFAULT 而非抛异常;DEFAULT 不发类名),有自己的测试钉着,
+   收敛需要新增「连接器 → fe-filesystem-api」依赖。不做。
+
+---
+
+## 8. 诚实声明:尚未验证的部分
+
+- **没跑任何 maven 构建**:`tasklist.md` 里的验证命令是开好的方子,**不是已跑的结果**。
+- **没查 apache/doris master** 是否有 `StorageAdapter.getAwsCredentialsProvider()` 
的调用者
+  ⇒ FPC-02 的 rebase 冲突风险是**陈述**不是实测。
+- **没跑 e2e**(需要集群)。相关套件(`regression-test/suites/aws_iam_role_p0/*`)只做了 grep:
+  用的是 `INSTANCE_PROFILE` / `CONTAINER` / `ANONYMOUS` 这类规范名,A/B 两个枚举都同样接受;
+  全仓唯一的 `v1` 行是注释掉的(`test_tvf_anonymous.groovy:30`)。
+- **`ExternalCatalog.buildHadoopConfiguration(Map)` 的调用者没有枚举** ⇒ FPC-04 明确排除它。
+- **FE 侧的 hadoop s3a client 是否真的会去实例化 `fs.s3a.aws.credentials.provider` 
里点名的类**,
+  没有端到端追到底:发出与传播链路追到了(`StorageAdapter.java:730,:739` → 
`CatalogProperty.java:383`
+  → `ExternalCatalog.java:209,:252`),**终端消费者没追**。这决定了 §3.3 差异 (1) 的严重程度上限。
diff --git a/plan-doc/fecore-property-cleanup/open-decisions.md 
b/plan-doc/fecore-property-cleanup/open-decisions.md
new file mode 100644
index 00000000000..683d99ddb14
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/open-decisions.md
@@ -0,0 +1,115 @@
+# 需要拍板的决策清单
+
+> 只放**动手前必须先定**的事。每条给出背景、选项、代价、推荐值。
+> 拍板后**就地**标 ✅ 并写明结论与日期,**不要删除条目**(历史要留痕)。
+> 细节与证据在 [`design.md`](./design.md),这里不重复。
+>
+> 下面条目里的「我」指做调研的那个 session,「你」指做决定的人。
+
+---
+
+## ⬜ OD-1 —— 删掉 metastore 后,null-supplier 分支该 fail-loud 还是 fail-silent?
+
+**阻塞**:[`tasklist.md`](./tasklist.md) **FPC-03**。不定这条就不能写 FPC-03 的代码。
+
+### 背景(先讲清楚在说什么)
+
+`CatalogProperty` 有个方法叫 `resolveDerivedStorageDefaults()`,作用是**给存储配置补默认值**。
+比如你建了个 iceberg hadoop 目录,只写了 `warehouse=hdfs://myns/wh`,没写 `fs.defaultFS`,
+那么系统要能自己推出 `fs.defaultFS=hdfs://myns` —— 这个推导就叫「派生存储默认值」。
+
+今天它的逻辑是**二选一**:
+
+```java
+if (pluginSupplier != null) {
+    return pluginSupplier.get();     // ← 路 A:问连接器要(插件目录走这条)
+}
+return getMetastoreProperties().getDerivedStorageProperties();   // ← 路 
B:fe-core 自己算(要删的)
+```
+
+FPC-03 要删掉路 B。问题是:**路 A 的 `pluginSupplier` 为 null 时怎么办?**
+
+### 为什么会有「supplier 为 null」这种时刻
+
+`PluginDrivenExternalCatalog` 的初始化顺序是:
+
+```
+:150  createConnectorFromProperties()      ← 先造连接器
+        └─ :206-208 造 DefaultConnectorContext,此时已经把 catalogProperty 的
+                     storage supplier 接上去了
+:177  setPluginDerivedStorageDefaultsSupplier(...)   ← 后装派生 supplier
+```
+
+也就是说,**在第 150 行到第 177 行之间存在一个窗口**:context 已经能访问 storage 了,
+但派生 supplier 还是 null。
+
+**这个窗口今天走不进去**(我逐个复核过):
+- `PaimonConnector` 构造函数(`:151-166`)只传了个方法引用 `this::pluginAuthenticator`,
+  那是**惰性**的(`:174-186` double-check 之后才真正计算),构造时不碰 storage
+- `IcebergConnector` 构造函数(`:215+`)同样
+- `HiveConnector` / `HudiConnector` 构造函数里**没有任何** `storage()` 调用
+- `validateProperties` 显式传 `Collections.emptyMap()`,javadoc 明写「验证不需要 storage」
+
+**所以这不是一个今天存在的 bug,而是一个「万一将来有连接器在构造期碰 storage,会怎样」的问题。**
+
+### 关键:删除会把「响亮报错」变成「静默出错」
+
+| | supplier 为 null 时会发生什么 |
+|---|---|
+| **今天** | 走路 B → `MetastoreProperties.create(props)` → 注册表里没有 
iceberg/paimon/hms 的工厂 → **抛 `IllegalArgumentException`**,FE 日志里响亮报错 |
+| **删完之后(若写 `return emptyMap()`)** | 静默返回空 → 丢掉 `warehouse → fs.defaultFS` 的桥接 
→ **而且**因为 
`setPluginDerivedStorageDefaultsSupplier`(`:279-281`)**故意不重置缓存**,这个错误的 
`StorageBindings` 会被**永久缓存**到下次 ALTER |
+
+一个 HA nameservice 的 hadoop iceberg 目录会因此**绑不上 HDFS**,而且不报错。
+
+### 三个选项
+
+| | 做法 | 代价 |
+|---|---|---|
+| **A(推荐)** | null 分支写 `throw new IllegalStateException("...")` | 
**精确保留今天的行为**(今天走到这里就是抛)。不新增 fe-core 能力(是替换现有 throw,不是发明新逻辑),守铁律 A。守 Rule 
12「fail loud」。<br>⚠️ 唯一不精确之处:今天属性图为**空**时 `getMetastoreProperties()` 返回 null 
而**不抛**(`:327-329`),最终得到 `emptyMap`;选 A 会变成抛。但空属性图的目录本来也没有存储可派生,且 
`RemoteDorisExternalCatalog`/`TestExternalCatalog` 根本不走 storage 路径 |
+| **B** | null 分支写 `return Collections.emptyMap()` | 代码最简,但**把响亮失败变成静默降级**。违 
Rule 12。今天不可达,但一旦将来某个连接器在构造期碰 storage,这就是个查半天的幽灵 bug |
+| **C(不推荐)** | 照调研报告原方案,把 `setPluginDerivedStorageDefaultsSupplier` 
那段**语句提前**到造连接器之前 | **修不干净**:提前之后 lambda 捕获的 `connector` 
字段**仍然是旧值/null**(`connector = newConnector` 发生在 
`createConnectorFromProperties()` 返回之后),所以窗口内照样拿到 `emptyMap`。是 churn 
不是修复。<br>⚠️ 这是调研报告把它列为 HIGH 风险前置项的方案,我复核后判定**无效**,特此记下免得下次又被报告带偏 |
+
+### 我的推荐
+
+**选 A。** 理由:它是唯一「零行为变更 + 守 fail-loud」的写法,代码量和 B 一样是一行,
+而且把一个**今天靠人工审计才知道不可达**的窗口,变成**万一走进去会立刻自曝**。
+
+> **拍板结果**:(待填)
+> **日期**:(待填)
+
+---
+
+## ⬜ OD-2 —— FPC-02(删 AWS 死构造臂)做不做?
+
+**不阻塞任何其它任务**,可以随时决定,也可以永远不做。
+
+### 背景
+
+`StorageAdapter.getAwsCredentialsProvider()`(`:383-458`,含两个私有 helper)和
+`AwsCredentialsProviderFactory` 的 `createV2` / `createDefaultV2` / 单参 
`getV2ClassName`
+加起来约 **146 行,零调用者**(我复核过:全仓只有它自己的声明和 javadoc)。
+
+### 唯一的顾虑
+
+这段代码是从上游 `f499c78c67c`(#66004)**整体带进来**的。如果 apache/doris master 上有、
+或将来加了调用者,下次 rebase 就会撞 modify/delete 冲突。
+
+**我没有查上游 master**(`design.md` §8 已如实声明)。
+
+### 选项
+
+| | 做法 |
+|---|---|
+| **A(推荐)** | 先 grep 一次上游 master;无调用者就删。删了 146 行死代码,且让 `common/` 
的存活理由更清晰(剩下的都是真在用的) |
+| **B** | 不做。反正是死代码,留着不碍事,省掉一次潜在的 rebase 冲突 |
+
+**我的推荐:A**,但**低优先级**——它和主线(FPC-03)完全解耦,什么时候做都行。
+
+> **拍板结果**:(待填)
+> **日期**:(待填)
+
+---
+
+## ✅ 已拍板
+
+(暂无。第一条拍板后移到这里,保留原文并补上结论与日期。)
diff --git a/plan-doc/fecore-property-cleanup/progress.md 
b/plan-doc/fecore-property-cleanup/progress.md
new file mode 100644
index 00000000000..1a064f44bbd
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/progress.md
@@ -0,0 +1,90 @@
+# 📜 进度记录(append-only)
+
+> **只追加,不覆盖**。新条目写在**底部**(时间正序)。
+> 每条格式:日期 /(commit)/ 做了什么 / 结论 / 踩了什么坑。
+> 「下一步做什么」不写这里(在 [`HANDOFF.md`](./HANDOFF.md));「勾到哪了」不写这里(在 
[`tasklist.md`](./tasklist.md))。
+
+---
+
+## 2026-07-28 — 任务空间建立 + 调研完成(FPC-00 / FPC-01a)
+
+**基线**:`3468d905eb3`,分支 `catalog-spi-review-21`。**代码零改动。**
+
+### 起因
+
+用户提出独立调研任务:fe-core `datasource/property/` 下的 `common` 和 `metastore` 两个目录,
+能不能删掉、或者迁进 `fe/fe-connector`?给出方案或给出不能的理由。
+背景原则:元数据服务(Glue 之类)归 `fe-connector-metastore-api`,存储(hdfs/s3 之类)归 
`fe-filesystem`。
+用户明确要求「不要局限于当前逻辑,必要时可增删任意模块的接口」。
+
+### 做法
+
+Workflow 编排:8 路并行侦察(产出 172 条 finding)→ 3 路独立设计
+(minimal / architectural / risk 三种视角,**结论并不一致**)→ 6 项对抗验证(每项被推翻后再走第二轮独立复核)
+→ 综合。之后由本 session 对**承重结论逐条亲自复核代码**(不只采信 agent)。
+
+三份设计的裁决分歧本身是有信息量的:
+
+| 视角 | `common/` | `metastore/` |
+|---|---|---|
+| minimal | KEEP | DELETE |
+| architectural | DELETE_AND_REDIRECT | DELETE |
+| risk | KEEP | DELETE_AND_REDIRECT |
+
+⇒ `metastore/` **三路一致要删**;`common/` **2:1 主张留**,且主张删的那路(architectural)
+自己也承认需要用户先拍两个板。最终采纳「留」。
+
+### 结论
+
+- **`metastore/`(4 文件 333 行)→ 整体删除**,连带孤儿 `ConnectionProperties.java`(140 行)。
+  运行期两道门都不可达;包里本来就零解析(唯一注册的 `TrinoConnectorPropertiesFactory` 连
+  `initNormalizeAndCheckProps()` 都不调);持久化无坑;接班人
+  (`fe-connector-metastore-api` 的 `MetaStoreProperties` + 
`MetaStoreProviders.bind` +
+  `Connector.deriveStorageProperties`)早已在生产路径上跑着 ⇒ **没有任何东西需要搬**。
+- **`common/`(2 文件 237 行)→ 留在 fe-core,只砍死的一半(~146 行)**。
+  它服务的是**内部存储**(冷存 StoragePolicy / 云上 StorageVault / TVF / backup / export),
+  零个 fe-connector、零个 fe-filesystem 模块 import 它 —— **根本不在这次迁移的射程内**。
+
+### 🔴 踩坑记录(最有价值的部分)
+
+**坑 1 —— 「重复造轮子」的误判,被对抗验证两轮推翻。**
+我的初判是:`common/` 和 `fe-filesystem-s3-base` 的
+`S3CredentialsProviderType`/`S3CredentialsProviderFactory` 是同一套逻辑的两份副本,
+应该「删除 + 把消费者指向现成实现」。**错。** 两条活的行为差异:
+① 发给 hadoop 的凭证串会多出 `ProfileCredentialsProvider`;
+② 模式串接受面放宽(空串 / `ENVIRONMENT` / `WEB_IDENTITY_TOKEN_FILE` 从抛异常变成接受,
+而 `StorageAdapter.java:169-170` 注释明说这个严格是**故意的**)。
+**且全仓没有任何测试钉住那个串** ⇒ 换掉会绿着上线一个回归。
+**通用教训**:「两个类长得像 ⇒ 可以合并」是个高频误判;判定等价必须**逐字段比对输出**
+(尤其是会发到 BE / 写进 hadoop conf 的**字符串**),而不是比对结构。
+顺带一提,仓库里其实有**四份**这套逻辑(第三份在 `fe-connector-iceberg`,第四份在
+`fe-connector-metastore-iceberg`),而且第三份的语义又和前两份都不同(未知模式回退 DEFAULT 而非抛)。
+
+**坑 2 —— 对抗验证抓到「两份候选设计会挂在 checkstyle 上」。**
+`fe/pom.xml:177-183` 把 checkstyle `check` 绑到**每个模块的 validate 阶段**,
+`checkstyle.xml:167` 开着 `UnusedImports` 且 `severity=error` ⇒
+**漏删一个 import,不带 flag 的 `mvn test` 会在跑任何测试之前就中止**。
+三份设计里两份的 import 清单是错的(漏了 `CatalogProperty.java:25` 的 `Preconditions`;
+另有一份把 `StorageAdapter` 的 import 错记到 `AwsCredentialsProviderFactory` 头上)。
+`tasklist.md` 里是**修正后**的精确清单。
+
+**坑 3 —— 调研报告自己给的 HIGH 风险缓解方案,复核后是无效的。**
+报告主张「把 `setPluginDerivedStorageDefaultsSupplier` 语句提前到造连接器之前」来关闭
+null-supplier 窗口。复核发现**修不干净**:提前之后 lambda 捕获的 `connector` 字段仍是旧值/null
+(`connector = newConnector` 发生在 `createConnectorFromProperties()` 返回**之后**),
+窗口内照样得到 `emptyMap`。
+更进一步,我逐个复核了四个连接器的构造函数(paimon `:151-166` / iceberg `:215+` / hive / hudi),
+**没有任何一个在构造期碰 storage**(都只传惰性方法引用 `this::pluginAuthenticator`),
+`validateProperties` 还显式传 `Collections.emptyMap()` ⇒ **这个窗口今天根本不可达**。
+所以它不是 blocker,而是「删完之后要 fail-loud 还是 fail-silent」的选择题 → 收敛成 **OD-1**。
+**通用教训**:**调研报告(哪怕是自己多轮对抗产出的)的「风险 + 缓解」也要复核**,
+高危标注可能是理论可达性而非真实可达性,配的解法可能治标不治本。
+
+### 产出
+
+本任务空间 `plan-doc/fecore-property-cleanup/`:
+`README.md` · `design.md` · `tasklist.md` · `open-decisions.md` · `HANDOFF.md` 
· `progress.md`。
+
+### 卡点
+
+**OD-1 待用户拍板**(null-supplier 分支 fail-loud vs fail-silent),阻塞 FPC-03。
diff --git a/plan-doc/fecore-property-cleanup/tasklist.md 
b/plan-doc/fecore-property-cleanup/tasklist.md
new file mode 100644
index 00000000000..38d5c98acdf
--- /dev/null
+++ b/plan-doc/fecore-property-cleanup/tasklist.md
@@ -0,0 +1,177 @@
+# ✅ Task List — 清理 fe-core `datasource/property/{common,metastore}`
+
+> **本任务的唯一进度清单**。完成一项即把 `[ ]` 勾成 `[x]`(随 commit 更新)。
+> **「怎么做」看 [`design.md`](./design.md),「下一步做什么」看 [`HANDOFF.md`](./HANDOFF.md),
+> 「还没定的事」看 [`open-decisions.md`](./open-decisions.md)。**
+> **⚠️ 行号信 HEAD 不信文档**(基线 = 2026-07-28 / `3468d905eb3`)。
+> 状态:⬜ 未开始 | 🚧 进行中 | ✅ 完成 | ⛔ blocked。编号永不复用。
+
+---
+
+## 🎯 总判据(唯一的「做完了没」标准)
+
+```bash
+R=/mnt/disk1/yy/git/wt-catalog-spi
+
+# ① fe-core 不再有 metastore 属性代码(基线 5 文件 → 0)
+ls $R/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/ 
2>/dev/null | wc -l   # → 0
+test -f 
$R/fe/fe-core/src/main/java/org/apache/doris/datasource/property/ConnectionProperties.java
   # → 不存在
+
+# ② 全仓无残留引用(含 groovy / 注释 / 文档)
+grep -rIn 
'MetastoreProperties\|MetastorePropertiesFactory\|AbstractMetastorePropertiesFactory\|TrinoConnectorPropertiesFactory\|ConnectionProperties\|checkMetaStoreAndStorageProperties\|getMetastoreProperties'
 \
+     $R/fe $R/regression-test $R/tools $R/gensrc --exclude-dir=target        # 
→ 空
+
+# ③ common/ 只剩活代码
+grep -rn 'createV2\|createDefaultV2\|getAwsCredentialsProvider()' $R/fe 
--exclude-dir=target          # → 空
+
+# ④ 编译 + 门禁全绿(每步都要,不只最后一次)
+mvn -f $R/fe/pom.xml -T 1C clean test-compile -Dcheckstyle.skip=true
+mvn -f $R/fe/pom.xml -pl fe-core checkstyle:check
+```
+
+**⚠️ 三条纪律**(本仓库已知踩坑,见 `design.md` §5):
+1. 删除类改动**不能只信增量编译** → 每步先 `rm -rf fe-core/target/{classes,test-classes}`。
+2. 全反应堆**必须含测试源**,**禁 `-Dmaven.test.skip=true`**;且必须 `-Dcheckstyle.skip=true`
+   (否则 checkstyle 扫 generated-sources 退化成平方级,构建卡死)。
+3. checkstyle 的 `UnusedImports` 是**阻塞门禁**,改为**只对改动模块**单独跑 `checkstyle:check`。
+
+---
+
+## 阶段 0 — 调研(✅ 已完成)
+
+- [x] **FPC-00** 事实基线:8 路并行侦察(172 条 finding)+ 3 路独立设计(minimal / architectural 
/ risk)
+      + 6 项对抗验证(**2 项被推翻**)→ [`design.md`](./design.md)
+      - 🔴 **被推翻的初判必读**:`common/` **不是** `fe-filesystem-s3-base` 的可替换双胞胎
+        (两条活的行为差异,`design.md` §3.3)。这是本轮最容易重犯的错。
+- [x] **FPC-01a** 承重结论逐条复核(本文作者亲验,非仅采信 agent):
+      `metastore/` 唯一 import 者 · 两道门皆不可达 · `ConnectionProperties` 在 
`StorageAdapter:856`
+      仅是 javadoc · `getAwsCredentialsProvider()` 零调用者 · fe-core pom 无 s3-base 
依赖 ·
+      **四个连接器构造函数均不碰 storage ⇒ null-supplier 窗口今天不可达**
+
+---
+
+## 阶段 1 — 🔴 前置拍板(**不拍板不许开工 FPC-03**)
+
+- [ ] **FPC-01** ⬜ **OD-1 拍板**:删掉 metastore 
后,`resolveDerivedStorageDefaults()` 的 null-supplier
+      分支要 **fail-loud(`throw`)** 还是 **fail-silent(`return emptyMap()`)**?
+      详见 [`open-decisions.md`](./open-decisions.md) **OD-1**(含推荐值与三个选项的代价)。
+      - ⚠️ 这条**改变 FPC-03 的代码**,必须先定。
+      - ⚠️ 调研报告原提的缓解方案「把 supplier 安装语句提前」**经复核修不干净**
+        (lambda 读到的 `connector` 字段仍是旧值/null),已在 OD-1 中列为**不推荐**。
+
+---
+
+## 阶段 2 — 删死代码(独立,可先做,也可整项丢弃)
+
+- [ ] **FPC-02** ⬜ 删 AWS provider 的死构造臂(**~146 行,零行为变更**)
+      - **文件**:
+        - 
`fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java`
+        - 
`fe/fe-core/src/main/java/org/apache/doris/datasource/property/common/AwsCredentialsProviderFactory.java`
+        - 
(仅注释)`fe/fe-connector/fe-connector-iceberg/.../AwsCredentialsProviderModes.java:36-37`、
+          `IcebergConnector.java:1113-1114`
+      - **删**:
+        - `StorageAdapter`:`getAwsCredentialsProvider()`(`:383-416` 含 javadoc)、
+          
`staticAwsCredentialsProvider(...)`(`:418-429`)、`s3AwsCredentialsProvider(...)`(`:431-458`)
+        - `AwsCredentialsProviderFactory`:`createV2(mode,boolean)`(`:46-68`)、
+          `createDefaultV2(boolean)`(`:80-99`)、**单参** 
`getV2ClassName(mode)`(`:136-162`)
+      - **import 精确修剪**(⚠️ 三份候选设计里有两份这里是错的):
+        - `StorageAdapter` 删 8 个:`AnonymousCredentialsProvider` 
`AwsBasicCredentials`
+          `AwsCredentialsProvider` 
`AwsSessionCredentials`(`:38-41`)、`StaticCredentialsProvider`(`:43`)、
+          
`Region`(`:44`)、`StsClient`(`:45`)、`StsAssumeRoleCredentialsProvider`(`:46`)
+        - `AwsCredentialsProviderFactory` 删 **2 个**:`:26` 
`AwsCredentialsProvider` **和**
+          `:27` `AwsCredentialsProviderChain`
+          (⚠️ 某份设计写的 "StaticCredentialsProvider 族" **在本文件里不存在**,它在 
`StorageAdapter.java:43`)
+      - **✋ 
必须保留**:`StorageAdapter.getAwsCredentialsProviderMode()`(`:379-381`)+ 
`s3CredentialsMode` 字段
+        (被 `AzureGuessRoutingParityTest` 钉住,且喂 `:614` 这个**活的** BE 值);
+        `StorageAdapter` 的 `InstanceProfileCredentialsProvider` 
import(`:42`,`:731` 在用);
+        `AwsCredentialsProviderFactory.getV2ClassName(mode, 
boolean)`(`:101-134`)+ 两个 env 探针(`:70-78`)
+      - **验收**:
+        ```bash
+        R=/mnt/disk1/yy/git/wt-catalog-spi
+        grep -rIn 'getAwsCredentialsProvider()\|createV2\|createDefaultV2' 
$R/fe $R/regression-test --exclude-dir=target
+        rm -rf $R/fe/fe-core/target/classes $R/fe/fe-core/target/test-classes
+        mvn -f $R/fe/pom.xml -T 1C clean test-compile -Dcheckstyle.skip=true
+        mvn -f $R/fe/pom.xml -pl fe-core test -Dcheckstyle.skip=true 
-DfailIfNoSpecifiedTests=false \
+            
-Dtest='AzureGuessRoutingParityTest,S3ThriftAdapterParityTest,CloudObjectStoreAdapterParityTest,LocationPathTest,DefaultConnectorContextBackendStoragePropsTest,DefaultConnectorContextNormalizeUriTest'
+        mvn -f $R/fe/pom.xml -pl fe-core checkstyle:check   # 阻塞项:证明 import 
修剪精确
+        ```
+      - 🟢 **可整项丢弃**:不影响 FPC-03。落地前 grep 一次上游 master 是否有
+        `getAwsCredentialsProvider()` 调用者(rebase 冲突风险,`design.md` §5)
+
+---
+
+## 阶段 3 — 主删除(**依赖 FPC-01 拍板**)
+
+- [ ] **FPC-03** ⬜ 退役整个 `metastore/` 集群 + 孤儿 `ConnectionProperties`
+      (**删 5 文件 473 行 + 从 `CatalogProperty` 挖掉 ~45 行;零 pom / 零连接器 / 零 
fe-filesystem 改动**)
+      - **`CatalogProperty.java` 改动**:
+        1. `resolveDerivedStorageDefaults()`(`:264-272`)→ 只走 supplier;
+           null 分支按 **FPC-01 的拍板结果**写(`throw` 或 `return 
Collections.emptyMap()`)
+        2. 更新其 javadoc(`:257-263` 里 `{@link}` 了 `MetastoreProperties`)和 
`:274-278` 的 javadoc
+        3. 删字段 `metastoreProperties`(`:94`)+ `resetAllCaches()` 里的 
`this.metastoreProperties = null;`(`:189`)
+        4. 删 `checkMetaStoreAndStorageProperties(Class)`(`:307-321`)和 
`getMetastoreProperties()`(`:323-345`)
+        5. **import 精确修剪**:`:20` `UserException`(只被 `:312`/`:336` 用)、`:21` 
`MetastoreProperties`、
+           **`:25` `Preconditions`(只被 `:316-317` 用 —— ⚠️ 两份设计漏了这个)**、
+           `:29` `ExceptionUtils`(只被 `:314`/`:339` 用)、`:30-31` 
`LogManager`/`Logger`,
+           以及 **`:48` 的 `LOG` 字段**(唯一使用点是 `:337`)。
+           **✋ 保留** `MapUtils`(`:250` 在用)和 `Collections`。
+      - **`git rm`**:
+        - `property/metastore/MetastoreProperties.java`
+        - `property/metastore/MetastorePropertiesFactory.java`
+        - `property/metastore/AbstractMetastorePropertiesFactory.java`
+        - `property/metastore/TrinoConnectorPropertiesFactory.java`
+        - `property/ConnectionProperties.java`(删 metastore 后成孤儿,`design.md` 
§2.5)
+      - **注释清理**(无编译影响,但守 Rule 12「不留悬空引用」):
+        - `StorageAdapter.java:856` —— 那是 **javadoc 散文**,改写掉即可,**不是**编译依赖
+        - `fe-connector-paimon/.../TcclPinningConnectorContext.java:49`
+        - 
`fe-core/src/test/.../CatalogPropertyPluginStorageDerivationTest.java:33-34,:53-54`
+      - **⚠️ `CatalogPropertyPluginStorageDerivationTest` 不许删也不许合并**:三个用例都装了 
supplier
+        (`:55`/`:74`/`:83`),删除后**照常通过**;但它注释里写的变异(「把
+        `resolveDerivedStorageDefaults` 改回走 
`getMetastoreProperties()`」)**变得无法表达** ⇒
+        按 Rule 9 把变异描述**改钉到一个仍然存在的变异上**。它是插件派生路径的唯一守卫。
+      - **验收**:
+        ```bash
+        R=/mnt/disk1/yy/git/wt-catalog-spi
+        grep -rIn 
'MetastoreProperties\|MetastorePropertiesFactory\|AbstractMetastorePropertiesFactory\|TrinoConnectorPropertiesFactory\|ConnectionProperties\|checkMetaStoreAndStorageProperties\|getMetastoreProperties'
 \
+             $R/fe $R/regression-test $R/tools $R/gensrc --exclude-dir=target
+        rm -rf $R/fe/fe-core/target/classes $R/fe/fe-core/target/test-classes
+        mvn -f $R/fe/pom.xml -T 1C clean test-compile -Dcheckstyle.skip=true
+        mvn -f $R/fe/pom.xml -pl fe-core test -Dcheckstyle.skip=true 
-DfailIfNoSpecifiedTests=false \
+            
-Dtest='CatalogPropertyPluginStorageDerivationTest,CatalogPropertyEffectiveRawStoragePropsTest,HmsGsonCompatReplayTest,IcebergGsonCompatReplayTest,PaimonGsonCompatReplayTest,PluginDrivenExternalCatalog*Test'
+        # 🔴 录制基线必须显式跑(全反应堆 test-compile 不跑 surefire —— 本分支已知盲区)
+        mvn -f $R/fe/pom.xml -pl fe-connector/fe-connector-api test 
-Dcheckstyle.skip=true
+        mvn -f $R/fe/pom.xml -pl fe-core checkstyle:check
+        ```
+        **预期不需要刷 `connector-metadata-methods.txt`**(fe-connector-api 不依赖 
fe-core,
+        72 行基线只用 `connector.api.*`/`java.*` 类型)—— **一旦红了就停手,别顺手刷基线。**
+
+---
+
+## 阶段 4 — 可选清扫(**另起提交,落地前重新 grep**)
+
+- [ ] **FPC-04** ⬜ 清扫 fe-core 已死的 storage 门
+      - **仅当执行时重新 grep 确认零调用者**才做:
+        
`ExternalCatalog.getHadoopProperties()`、`ExternalCatalog.getConfiguration()`(已标 
`@Deprecated`)
+        + `buildConf()` 及其缓存字段、`CatalogProperty.getBackendStorageProperties()`、
+        `CatalogProperty.getOrderedStorageAdapters()`
+      - **✋ 不要碰** `ExternalCatalog.buildHadoopConfiguration(Map)` —— 
它的调用者**没有枚举过**
+      - **收益**:做完后 `PluginDrivenExternalCatalog.java:207-208` 成为 
`initStorageAdapters()` 的
+        **唯一入口(由构造保证,而非靠人工审计)**
+      - **验收**:逐符号零调用者 grep → **完整** `mvn -pl fe-core test 
-Dcheckstyle.skip=true --fail-at-end`
+        (⚠️ 它动的是每个 catalog 都继承的基类,**窄 `-Dtest` 列表不够**)→ `checkstyle:check`
+      - 🟢 刻意与 FPC-03 分开,好让 FPC-03 保持**可单独回滚**
+
+---
+
+## 📋 单列后续(**不并入本任务**,各自开 ticket)
+
+- **SEP-1** `StorageAdapter.checkAzureOauth2OnlyForIcebergRest()`(`:822-843`)在 
storage 路径读
+  metastore 命名空间键(`type` / `iceberg.catalog.type`)—— 真的 ARCH-GOAL 违规,但带着上游 
#66004
+  刻意的大小写敏感怪癖,需单独一刀 + e2e。
+- **SEP-2** architectural 视角的完整 S3–S5 轨道(把 `S3CredentialsProviderType` 上提 
`fe-filesystem-api`
+  → 调和 `hadoopClassName` → 删 `common/`)。**架构上是对的终局**,但需用户对
+  `Config.aws_credentials_provider_version` v1 分支(`Config.java:3740`)+「发哪条 
DEFAULT 链」拍板,
+  且会改线上串。见 `design.md` §7-2。
+- **SEP-3** `constants/BaseProperties.getCloudCredential(...)` 零调用者,唯一作用是当 
`AIProperties` 的空父类。
+- **SEP-4** `fe-connector-metastore-api/pom.xml:64` 注释「This module is compiled 
into fe-core」是
+  **过时的假话**(无任何 pom 声明它;它打进各插件 zip)——顺手改掉。


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

Reply via email to