# CSP-006　技術棧與開發環境基線

| 項目 | 內容 |
|---|---|
| 文件編號 | CSP-006 |
| 版本 | 1.0 |
| 狀態 | 已核定 |
| 撰寫者 | SA / Tech Lead |
| 審查者 | PMO / Security Architect / SRE |
| 對應需求 | NFR-SYS 群組、NFR-QUAL 群組；SAD-001 §1.2 架構原則、SAD-006 §1 雲原生原則 |

---

## 1. 目的與適用範圍

本文件為 `cardcore` 全案的**技術棧單一真實來源（Single Source of Truth）**，回答三個問題：

1. **用什麼技術**——語言、框架、資料庫、訊息、觀測、測試工具，逐項鎖定版本。
2. **在哪裡跑**——本地、SIT、UAT、PERF、PROD 各環境的組態基線與切換方式。
3. **與既有文件的落差**——本文件生效後，哪些已核定文件須回改（見 §6）。

**適用範圍**：後端 17 個微服務、平台元件（API 閘道、BFF、共用程式庫）、React 前端、
建置與部署腳本、CI 品質關卡。

**與 CSP-001 的關係**：CSP-001 §1 載明「正式開發環境由客戶於程式撰寫階段拍板，本文件以
Java 21 + Spring Boot 3.2 為**建議基線**」。客戶已拍板，故本文件取代 CSP-001 §1 之建議基線敘述；
CSP-001 之**架構、命名、禁止事項、品質門檻全部繼續適用**（該文 §1 後段已明文授權此種分工）。

---

## 2. 技術棧基線

> **版本鎖定原則**：下表版本為**凍結值**。任何升級須走 §7 變更控制，不得由 Coder 自行調整。
> 所有版本皆以 2026-09-18 之 Maven Central / Docker Hub 實際可用版本為準，非推測值。

### 2.1 語言與執行環境

| 項目 | 鎖定版本 | 選定理由 |
|---|---|---|
| JDK | **Temurin 21.0.12.1+1 LTS** | LTS，支援至 2031+；與 CSP-001 基線一致；銀行案生態最穩 |
| 語言 | **Java 21** | CSP-001 §5 指定之靜態分析工具鏈（Checkstyle／SpotBugs／PMD）為 Java 原生；金融業人才池與外包團隊上手成本最低 |
| 語言特性使用界線 | 可用：record、sealed、pattern matching、text block<br>**限用**：虛擬執行緒需經 TL 同意後啟用 | 虛擬執行緒與 Oracle JDBC／Hibernate 之 pinning 行為需實測，不得預設可用 |

> **決議紀錄（Java vs Kotlin）**：CSP-002 §3 之服務骨架路徑原寫 `src/main/kotlin/`，與 CSP-001 §5
> 之 Java 工具鏈矛盾。經決議採 **Java 21**，CSP-002 §3 須回改（見 §6 第 2 項）。

### 2.2 應用框架

| 項目 | 鎖定版本 | 備註 |
|---|---|---|
| Spring Boot | **4.1.1** | 2026-06 起最新穩定線；最低 Java 17、最高相容 Java 26 |
| Spring Framework | **7.0.9** | Boot 4.1.1 之內含版本 |
| Spring Cloud | **2025.1.3** | ⚠️ 見下方風險說明 |
| Spring Security | 7.0.x | OAuth2 Resource Server、方法級授權 |
| Spring Data JPA / Hibernate | 7.x | 寫側聚合持久化 |
| jOOQ | 3.20.x | 讀側複雜查詢（Oracle 方言、報表）；**要求 Java 21**，已滿足 |
| Jackson | 3.1.x（`tools.jackson`） | Boot 4 已由 `com.fasterxml.jackson` 遷移至 `tools.jackson`，Coder 勿混用兩套 |

> ⚠️ **Spring Cloud 版本風險（必須在 Sprint 0 驗證）**
> Maven Central 事實：`spring-cloud-dependencies` 最新為 **2025.1.3**，其 POM 內宣告
> `spring-boot.version = 4.0.8`——即官方**尚未有對齊 Spring Boot 4.1 的 release train**
> （下一班為 2026.0，尚未發布）。
>
> 已執行實測（2026-09-18）：
> - 以 Boot **4.1.1** + Cloud **2025.1.3** 解析依賴並編譯 → **通過**（Framework 7.0.9、spring-cloud-commons 5.0.3）
> - 執行期啟動驗證結果見 §2.2.1
>
> **決策**：採 Boot 4.1.1 + Cloud 2025.1.3，並於 Sprint 0 設**版本相容性關卡**（§5.2）。
> 若關卡未過，降級組合為 **Boot 4.0.8 + Cloud 2025.1.3**（官方對齊，但 Boot 4.0 OSS 支援至 2026-12-31）。

#### 2.2.1 版本相容性驗證紀錄（2026-09-18 實測）

| 驗證項 | 方法 | 結果 |
|---|---|---|
| 依賴解析一致性 | Gradle `runtimeClasspath` 解析 | ✅ 通過，無版本衝突 |
| 編譯 | `gradle compileJava`（Java 21 toolchain） | ✅ 通過 |
| **執行期上下文啟動** | `gradle bootRun`，載入 web + actuator + OpenFeign + Resilience4j + Stream-Kafka + Vault 六項 starter | ✅ **通過**——Tomcat 啟動、`Started SpikeApp in 3.975 seconds`，無 `BeanCreationException`／`NoSuchMethodError`／`NoClassDefFoundError` |
| 實際解析版本 | — | Boot **4.1.1**／Framework **7.0.9**／spring-cloud-commons **5.0.3**／spring-cloud-context **5.0.3**／Jackson **3.1.5** |

> **實測結論**：Spring Boot 4.1.1 與 Spring Cloud 2025.1.3 之組合，在編譯期與執行期**均可正常運作**，
> 未出現 Spring Cloud 因對齊 Boot 4.0.8 而導致之 Bean 建立失敗。
>
> **驗證範圍限制（誠實揭露）**：本次啟動驗證已停用 `spring.cloud.vault` 與 `spring.cloud.config`
> （本地無對應服務）。此二者之執行期行為、以及 Spring Cloud Gateway 之實際路由，
> 仍須於 **Sprint 0 關卡 G-1** 以完整組態複驗。

### 2.3 建置與工具鏈

| 項目 | 鎖定版本 | 備註 |
|---|---|---|
| Gradle | **9.7.1** | 主建置工具（CSP-002 §3 之 `build.gradle.kts`、CSP-001 §5 之 `./gradlew check`）。Boot 4.1 要求 Gradle 8.14+ 或 9.x |
| Gradle Wrapper | 每個服務必附 | **CI 一律用 wrapper**，不依賴機器上的 Gradle |
| Maven | 3.9.9 | 備援；本專案不作為主建置工具 |
| Node.js | 22.22.2 LTS | 前端建置 |
| pnpm | 12.4.2 | 前端套件管理（經 corepack，不污染全域） |
| Docker | 29.8.0 + Compose v5.5.1 | 本地容器環境 |
| kubectl | 1.36.1 | 叢集操作 |
| Helm | 4.3.0 | K8s manifest 封裝 |

### 2.4 資料層：Oracle 主 + PostgreSQL 讀側

依客戶指定「Oracle 為主，PostgreSQL 於必要時採 Hybrid DB 架構」，分工如下：

| 用途 | 資料庫 | 版本 | 理由 |
|---|---|---|---|
| 權威 OLTP：授權、帳務、額度、卡片、產品參數 | **Oracle** | 本地 26ai Free（23.26.0）／生產 26ai Enterprise | 客戶指定；交易強一致、金額運算須精確 |
| 平台權威資料：Saga 協調狀態 | **Oracle**（`CC_PLT`） | 同上 | 跨服務元件，非任何單一服務所有（SDD-004 §5.3） |
| 平台共通表：Outbox、冪等紀錄、稽核軌跡 | **Oracle**（**各服務自己的 schema**） | 同上 | 依 SDD-001 §9「每服務各一份」。**Outbox 必須與該服務的權威寫入同 schema 同交易**（SDD-004 §4.3）；集中至共用 schema 會違反 DP-01 |
| CQRS 讀模型（投影） | **PostgreSQL** | 18.6 | 讀多寫少、與寫庫物理隔離（SAD-003 §3.1） |
| 事件流存檔（重播／稽核） | **PostgreSQL** | 18.6 | 只增不改、資料量大，避免佔用 Oracle 授權 |
| 報表與分析 | **PostgreSQL** | 18.6 | 允許延遲，避免以 Oracle 授權承擔分析負載 |
| 快取、冪等鍵 | **Redis** | 8.8.2 | 高頻讀寫、TTL 語意 |

**Schema 歸屬**（對應 SAD-003 DP-01「一服務一 schema」）：

```
Oracle
├── CC_AUTHORIZATION   CC_CARD       CC_LIMIT      CC_PARTY
├── CC_PRODUCT         CC_PRICING    CC_POSTING    CC_PAYMENT
├── CC_INSTALLMENT     CC_REWARDS    CC_STATEMENT  CC_DISPUTE
├── CC_CLEARING        CC_MERCHANT   CC_MIGRATION  CC_NOTIFICATION
├── CC_REPORT
│   └── 上述每個服務 schema 內各含三張平台共通表（SDD-001 §9）：
│       outbox / idempotency_records / audit_trail
└── CC_PLT             Saga 協調狀態（saga_instances / saga_steps，跨服務共用）

PostgreSQL
├── proj               CQRS 讀模型（投影器寫、查詢側只讀）
├── evt                事件流存檔
└── rpt                報表分析
```

| 項目 | 選型 |
|---|---|
| 資料庫遷移 | **Flyway**（Oracle 與 PostgreSQL 各用對應模組；腳本須冪等，CSP-002 §5） |
| 連線池 | HikariCP（Boot 內建） |
| Oracle 驅動 | `com.oracle.database.jdbc:ojdbc11` |
| 交易邊界 | 僅在 `application/` 層開交易（CSP-003 §6）；`domain/` 層無交易概念 |

> **注意**：本案採 Oracle 為主，SAD-003 §3 之資料庫部署圖（僅 PostgreSQL 讀寫叢集）與
> SDD-001 之 ER 圖語法須回改為 Oracle 方言對照（見 §6 第 3、5 項）。

### 2.5 訊息與事件

| 項目 | 鎖定版本 | 用途 |
|---|---|---|
| Apache Kafka | **4.3.1**（KRaft 模式，無 ZooKeeper） | 領域事件、跨服務狀態傳播（SAD-001 AP-05） |
| Spring for Apache Kafka | 隨 Boot 4.1.1 BOM | 生產者／消費者 |
| Spring Cloud Stream + Kafka Binder | 隨 Cloud 2025.1.3 | 事件綁定抽象 |
| Outbox 發送器 | **自建**（Spring `@Scheduled` + `SELECT ... FOR UPDATE SKIP LOCKED`） | Transactional Outbox（SDD-004 §4.3） |
| Schema 演進 | `schema_version` 欄位向後相容（CSP-002 §5） | 契約只增不減（SAD-001 AP-08） |
| 消費者冪等 | Redis `SETNX` + 事件 `eventId` | 至少一次投遞之去重（SDD-004 §4.1） |

> **為何不用 Debezium CDC**：Outbox 已與權威寫入同交易，以輪詢發送即可滿足
> 「事件不早於資料提交」；引入 CDC 會多一層 Oracle LogMiner 相依與維運負擔，
> 本地開發亦無法驗證。若未來吞吐成為瓶頸，再以 ADR 評估。

### 2.6 Saga 與跨服務一致性

SDD-004 §5 已定義兩種 Saga 型態，本節鎖定其實作方式：

| 型態 | 適用場景（SDD-004 §5.1） | 實作方式 |
|---|---|---|
| **編排式（Orchestration）** | 分期建立（佔用→入帳）、點數兌換（扣點→授權→帳務） | 自建 **Saga 協調者**：狀態機持久化於 Oracle `CC_PLT.SAGA_INSTANCES` / `SAGA_STEPS`，逐步推進並記錄正／補償動作 |
| **協同式（Choreography）** | 請款入帳（授權→帳務→額度） | 各服務監聽 Kafka 事件自行反應；補償由反向事件觸發 |

**設計要點**

1. **狀態機落在領域層**：Saga 之狀態轉換屬業務規則，置於 `domain/`；**不引入 Spring Statemachine**
   等框架相依——理由：① `domain/` 層不得依賴框架（CSP-001 §2、SAD-001 AP-03）；
   ② Spring Statemachine 對 Boot 4.x 之支援狀態尚未確認，不作為基線。
2. **每個補償動作必須冪等**（SDD-004 §5.2 對照表）；發送通知類動作不補償。
3. **補償失敗處理**：拋 `CC-PLT-5002`（Saga 補償失敗），進入人工處置佇列並告警（CSP-003 §6）。
4. **逾時**：同步呼叫下游預設 800ms 逾時，超過即 `CC-PLT-5001` 並觸發補償（CSP-003 §5）。

**ADR：為何不自建以外之方案**

| 候選 | 不採用之理由 |
|---|---|
| Axon Framework | 引入獨立 event store 與其自有持久化模型，與「Oracle 為權威來源」及 SAD-003 之 CQRS 投影設計衝突，形成兩套事件真相 |
| Temporal | 需額外部署 Temporal Server／Worker 叢集，維運成本與本案 17 服務規模不成比例 |
| Camunda / Zeebe | BPMN 引擎導向人工流程；本案 Saga 為系統間自動補償，且會新增一個狀態權威來源 |

### 2.7 API 與契約

| 項目 | 選型 | 備註 |
|---|---|---|
| 契約規格 | **OpenAPI 3.1** | `cardcore/contracts/openapi/*.yaml`（17 份）為唯一介面定義（SAD-001 AP-01） |
| 契約與實作一致性 | `openapi-diff` | CI 強制，不允許實作漂移（CSP-002 §5） |
| Mock 服務 | Prism | 前端與跨組開發期使用 |
| 契約模糊測試 | Schemathesis | 依 OpenAPI 自動生成測試案例 |
| API 閘道 | Spring Cloud Gateway | 路由、限流、鑑權入口 |
| 服務間呼叫 | OpenFeign（宣告式）+ Resilience4j（斷路器、重試、艙壁） | 逾時與退避須符合 CSP-003 §5 |

### 2.8 前端

| 項目 | 鎖定版本 | 備註 |
|---|---|---|
| React | **18.x** | 依已核定之 SDD-007 §7 與 CSP-002 §7；**不用 React 19** |
| Vite | **8.x**（實際 **8.3.2**） | 建置與開發伺服器。★ **2026-10-05 由 5.4.21 升至 8.3.2**（`CR-040`） |
| TypeScript | 5.x（`strict: true`） | 禁止 `any` 逃逸（ESLint 規則強制） |
| 套件管理 | pnpm 12.4.2 | — |
| 伺服器狀態管理 | TanStack Query | 對接 BFF；不快取業務敏感資料於 localStorage |
| 路由 | React Router **7.x**（實際 **7.18.4**） | ★ **2026-10-05 由 6.30.6 升至 7.18.4**（`CR-040`） |
| 程式碼風格 | ESLint（Airbnb 子集）+ Prettier | 0 error（CSP-001 §5） |

> **React 版本說明**：React 19 已發布，但 SDD-007 已核定為 React 18。升版屬破壞性變更，
> 須走 §7 變更控制並同步更新 SDD-007，本階段不變更。

> ★★ **2026-10-05 併發現之「規格 vs 實物落差」（`CR-040 §2`）**：
> 本表**原載「Vite 6.x」「React Router 7.x」**，惟**實際安裝者為 Vite 5.4.21／React Router 6.30.6**
> ——**規格宣告的版本從未落實**。`CR-040` 之升版**實質上把實物對齊了規格**（Vite 更進一步至 8.x）。
> ⚠️ **教訓**：**版本鎖定表必須與 `package.json`／`node_modules` 定期核對**——
> 否則會出現「文件說 7、實物是 6」之漂移，而**編譯與建置都不會報錯**。
> **建議**：納入 `TRP-002` 之 N2 批次（以工具核對規格表 vs 實際相依版本）。

### 2.9 測試

| 層級 | 工具 | 要求 |
|---|---|---|
| 單元測試 | JUnit 5 + AssertJ + Mockito | **不得啟動 Spring 容器**（CSP-002 §6.2）；覆蓋率依 CSP-005 |
| 架構測試 | **ArchUnit** | 強制 `interfaces → application → domain ← infrastructure` 依賴方向（CSP-002 §6.3），違反即建置失敗 |
| 整合測試 | **Testcontainers**（Oracle、PostgreSQL、Kafka） | 以真實資料庫驗證，禁止以 H2 模擬 Oracle |
| 契約測試 | Spring Cloud Contract / Pact | 消費者驅動契約 |
| API 模糊測試 | Schemathesis | 依 OpenAPI 生成 |
| 效能測試 | Gatling | 驗證 SAD-001 §1.1 之 G6（授權 P99 ≤ 200ms、≥ 1,000 TPS） |
| 覆蓋率 | JaCoCo | 門檻依 CSP-005 |
| 假測試防範 | `tools/scan_faketest.py` | CI 強制（CSP-005 §6） |

> ⚠️ **Testcontainers 與 Oracle**：需拉取 Oracle 映像，首次執行較慢；本機記憶體吃緊時
> 可改用 `--profile oracle` 之常駐容器並以 `SPRING_DATASOURCE_URL` 指向它。

### 2.10 靜態分析與品質門檻

| 對象 | 工具 | 門檻 | 依據 |
|---|---|---|---|
| Java | Checkstyle + SpotBugs + PMD | 0 error、0 critical warning | CSP-001 §5 |
| TypeScript | ESLint + Prettier | 0 error | CSP-001 §5 |
| YAML | yamllint | 0 error | CSP-001 §5 |
| SQL | `tools/validate_sql.py` | 0 error | CSP-001 §5（⚠️ 見 §6 第 6 項） |
| 禁止模式 | `scan_scaffold.py`／`scan_mockdata.py`／`scan_faketest.py` | 0 命中 | CSP-001 §8、CSP-005 §6 |
| Mermaid 語法 | `tools/validate_mermaid.py` | 0 錯誤 | 客戶硬性要求 |
| 提交訊息 | `<type>(<scope>): <subject>` + `Refs: FR-XX-NN` | 供 GOV-004 追溯 | CSP-001 §6 |

### 2.11 可觀測性

| 支柱 | 本地開發 | 生產 | 備註 |
|---|---|---|---|
| 軌跡 | OTel Collector → **Jaeger** 1.76.0 | OTel → Tempo / Grafana | OpenTelemetry 語意約定 |
| 指標 | **Prometheus** 3.14.0 + **Grafana** 12.4.11 | 同上（HA） | 每服務暴露 `/actuator/prometheus`（CSP-002 §3） |
| 日誌 | OTel Collector → **Loki** 3.7.8 | 同上 | 結構化日誌、必含 `trace_id`（CSP-004） |
| 追蹤識別碼 | W3C Trace Context + correlation-id 過濾器 | 同 | 貫穿服務與事件（SAD-001 AP-09） |
| 個資保護 | Collector `transform` processor 遮蔽卡號／身分證 | 同 | 遙測資料不得含明文個資（SAD-005 §5） |

### 2.12 安全

| 項目 | 選型 | 備註 |
|---|---|---|
| 身分驗證 | OAuth2 / OIDC；本地 **Keycloak 26.4**，生產接行內 IdP | JWT（RS256） |
| 授權 | Spring Security 7 + 方法級 `@PreAuthorize` | 最小權限（SAD-001 AP-10） |
| 密鑰管理 | 本地：`.env`（僅開發）；生產：**HashiCorp Vault** 或行內密鑰管理 | 禁止密鑰進版控（SAD-006 §2.1） |
| 欄位級加密 | AES-GCM（`common-security` 模組） | 卡號、身分證字號（SAD-005 §5） |
| 硬體加密 | HSM 經 PKCS#11 | PIN、CVV、金鑰 |
| 相依漏洞掃描 | OWASP Dependency-Check + Trivy（映像掃描） | CI 強制 |
| 靜態安全分析 | SpotBugs（含 FindSecBugs 規則） | — |

### 2.13 容器與編排

| 項目 | 選型 |
|---|---|
| 映像建置 | Docker 多階段建置；執行期用 JRE（非 JDK）映像 |
| 映像基底 | `eclipse-temurin:21-jre`（生產）；版本以 digest 鎖定 |
| 映像不可變 | 組態一律由環境變數或組態中心注入，**不打包進映像**（SAD-006 §1） |
| 編排 | Kubernetes（Active-Active 雙叢集，SAD-006 §3） |
| 本地叢集 | kind 或 k3d（Docker 內）——**僅供部署冒煙測試，非日常開發** |
| 封裝 | Helm 4.3.0 |
| 健康端點 | `GET /actuator/health`（liveness/readiness）、`/actuator/info`、`/actuator/prometheus` |

---

### 2.14 依賴版本覆寫（Override）——2026-10-05 新增（`CR-037`）

**原則**：BOM（`spring-boot-dependencies`）所管理之版本為**預設**；當 BOM **落後**於
安全修補時，**只覆寫落後的那一個版本**，不連帶動其餘相依。

| 被覆寫 | BOM 之版本 | 覆寫為 | 理由 | 依據 |
|---|---|---|---|---|
| `org.apache.tomcat.embed:tomcat-embed-{core,el,websocket}` | **11.0.24** | **11.0.26** | BOM pin 之 11.0.24 含 **3 個 CRITICAL**（`GHSA-9xv2-5v5q-p794`／`GHSA-gcx9-497g-6cp6`／`GHSA-h3x4-894j-xpx5`） | `CR-037` |
| **`com.fasterxml.jackson:jackson-bom`（2.x 線）** | **2.21.5** | **2.21.7** | 含 **5 個 HIGH ＋ 2 個 MODERATE**（`jackson-core`／`jackson-databind`；含 **ReDoS**） | `CR-038` |
| **`tools.jackson:jackson-bom`（3.x 線）** | **3.1.5** | **3.1.7** | 含 **5 個 HIGH ＋ 2 個 MODERATE**（同上，**不同分支、修復版本不同**） | `CR-038` |
| **`at.yawk.lz4:lz4-java`** | **（BOM 未管理）** | **1.11.4** | 含 **1 個 MODERATE**（原生 XXHash 可致 JVM 崩潰）。★ **不在 Spring Boot BOM 內**——係 `kafka-clients:4.2.1` 之**傳遞相依** → **機制 A** | `CR-039` |

#### 2.14.1 ★ 兩種覆寫機制（**選錯會漏**）

| 機制 | 做法 | 適用 | 實例 |
|---|---|---|---|
| **A　逐個 artifact constraint** | `constraints { add("implementation", "<g>:<a>:<v>") }` | 相依**不屬任何 BOM 家族**，或家族**極小且穩定** | `CR-037`（Tomcat——classpath 上**恰 3 件**，逐一列出不會漏） |
| **B　覆寫整個 BOM** ★ **優先** | 以 `platform("<bom 座標>")` **匯入較新之 BOM** | 相依**由 BOM 管理之大家族** | `CR-038`（Jackson——兩個 BOM 各管 **20+ artifact**；逐個列**必然有一天漏**） |

> ★★ **判準**：**先問「這個相依是不是某個 BOM 管的家族成員？」**
> 是 → **機制 B**（BOM 一次涵蓋全家族，語意即「這條線整體升級」）；
> 否 → 機制 A。
> ★ **機制 A 的風險**：classpath 上今天有 3 件、明天可能變 4 件（任何 starter 加進來就可能帶新的）。

> ★ **兩個 BOM 必須一起升**（`CR-038 §3.2`）：同一生態系若有**多條線**（如 Jackson 2.x／3.x），
> Spring Boot BOM **同時**管理兩者（`jackson-2-bom.version` 與 `jackson-bom.version`）——
> **只升一條 → 另一條的漏洞仍在**，且兩條之修復版本**不可混用**。

**機制**：於 `backend/build.gradle.kts` 之 `subprojects {}` 以 **Gradle 專案層 `constraints`** 宣告
（**非** `resolutionStrategy.force`——後者為最後手段）。
版本值取自 `backend/gradle/libs.versions.toml` 之 `tomcat`（**版本單一真實來源**）。

> ★ **本項為「BOM 落後」之標準處置，非特例**——`CR-037 §2` 已證
> **Spring Boot 4.1.x 僅到 4.1.1（無含修復之 patch）**，故「升 BOM」無路可走。
> ★ **新增任何覆寫前**，須先依 `§5.2` 之 Version Compatibility Gate 查證，
> 並於**本表逐列載明理由與依據**——**不得**無紀錄地改版號。

### 2.15 版本鎖定之**機器可讀**來源與核對（2026-10-05 新增，WBS 5.10）

**問題**：`§2.1～§2.8` 之版本表為**散文式 markdown**（含粗體、`7.0.x` 之類範圍、附註文字）
——**機器解析易碎**，且**「文件說幾版」與「實際幾版」之間沒有任何自動化核對**。

> ★★ **實證（`CR-040 §2`）**：本表**原載「Vite 6.x」「React Router 7.x」**，
> 而**實際安裝為 Vite 5.4.21／React Router 6.30.6**——**落後兩個大版**，
> 而 `tsc`／建置／測試／掃描器**全都不會報**。**這種漂移是無聲的。**

| 項目 | 內容 |
|---|---|
| **規格側之單一真實來源** | **`spec/versions.yaml`**（機器可讀；逐項載 `expect`／`match`／`actual` 來源／`csp` 節） |
| **核對工具** | **`python3 tools/verify_versions.py`** |
| **核對兩件事** | ① **規格 ↔ 實際**（Gradle version catalog／`node_modules`／`java -version`）<br>② **規格 ↔ 內文**（`CSP-006` 對應節是否**仍載有**該版本字串——防散文漂移） |
| **退出碼** | `0` ＝ 全部相符；`1` ＝ **有落差或無法解析**；`2` ＝ 執行錯誤（**fail-closed**） |
| **自測** | `python3 tools/verify_versions.py --selftest`（人工樣本回測判定力，同 `scan_scaffold.py` 之慣例） |

**★★ 變更版本時之強制流程**：

1. 改**實際**（`libs.versions.toml`／`package.json`／覆寫區塊）
2. **同步** `spec/versions.yaml` 之 `expect`
3. **同步**本節（`§2.1～§2.8`／`§2.14`）之**內文版本字串**
4. **執行 `verify_versions.py`，須為 `0`**

> ⚠️ **本節之第 3 步與第 4 步不可省略**——`verify_versions.py` 之**首次執行即抓到**
> 一處真實遺漏（`CR-039` 之 `lz4` 已改實際、**惟未回寫本節內文**），
> 證明**「改了程式沒改文件」是常態，必須靠工具攔**。

## 3. 開發環境切換（與 Legacy ECS 並存）

本機同時存在 **Legacy ECS**（Java 8 + Tomcat 9）與 **cardcore**（Java 21）兩套環境。

| 環境 | Java | 設定來源 |
|---|---|---|
| Legacy ECS | 8（Temurin 1.8.0_504） | `~/.zshrc`（全域預設） |
| cardcore | 21（Temurin 21.0.12.1 LTS） | `ops/local/env.sh`（當前 shell） |

```bash
source cardcore/ops/local/env.sh     # 切換至 Java 21（僅當前 shell）
```

**強制規則**：

1. **禁止**將 Java 21 寫入 `~/.zshrc` 或任何全域設定——會導致 Legacy ECS 之 Maven／Tomcat 編譯失敗。
2. CI 與建置一律使用 **Gradle Wrapper** 與 **Java Toolchain**（`languageVersion = 21`），
   不依賴機器預設 `JAVA_HOME`。
3. 工具鏈安裝位置沿用既有慣例 `~/dev-tools/opt/`（`install-toolchain.sh` 為冪等）。

---

## 4. 本地開發環境

詳見 `ops/local/README.md`。要點：

| 分層 | 服務 | 啟動方式 |
|---|---|---|
| 輕量核心（約 4GB） | PostgreSQL、Redis、Kafka | `docker compose up -d` |
| Oracle（約 4GB） | Oracle 26ai Free | `--profile oracle` |
| 可觀測性（約 3GB） | OTel Collector、Jaeger、Prometheus、Grafana、Loki | `--profile obs` |
| 工具 | Kafka UI | `--profile tools` |
| 身分驗證 | Keycloak | `--profile iam` |

**資源約束**：本機為 4 核 / 32GB，**禁止一次啟動全部 profile**。日常開發建議
「輕量核心 + 正在開發的 2–3 個服務」，需要時再加 Oracle。

**埠號衝突避免**：Legacy ECS 之 Tomcat 佔用 8080，故 cardcore 本地服務建議由 8081 起編。

---

## 5. 版本治理

### 5.1 凍結與升級

- §2 各表版本為凍結值；Coder 不得自行調整版本。
- 補丁版（patch）升級由 Tech Lead 決定，於 Sprint 內進行。
- 次版（minor）以上升級須走 §7 變更控制，並更新本文件與 §6 之回改清單。

### 5.2 Sprint 0 必過關卡（Version Compatibility Gate）

| # | 關卡 | 通過條件 |
|---|---|---|
| G-1 | Boot 4.1.1 + Cloud 2025.1.3 執行期相容 | 一個具備 web + actuator + OpenFeign + Resilience4j + Stream-Kafka + Vault 之服務可正常啟動並回應 `/actuator/health` |
| G-2 | Oracle + Flyway 遷移 | 17 個 schema 之基線遷移腳本可重跑（冪等）且 0 錯誤 |
| G-3 | Testcontainers 整合測試 | Oracle／PostgreSQL／Kafka 容器可拉起並完成一個 E2E 案例 |
| G-4 | ArchUnit 分層規則 | 可攔阻故意違反依賴方向之程式碼 |
| G-5 | Saga 骨架 | 編排式 Saga 之正向與補償路徑可跑通（含補償失敗落 `CC-PLT-5002`） |

**G-1 未過之降級方案**：改用 Spring Boot 4.0.8 + Spring Cloud 2025.1.3（官方對齊組合），
並於 2027 Q1 再評估升級至 Boot 4.2 + Cloud 2026.0。

---

## 6. 與已核定文件之衝突與回改清單

本文件生效後，下列已核定文件須回改。**回改前，本文件之敘述優先**。

| # | 文件 | 現況 | 應改為 | 依據 |
|---|---|---|---|---|
| 1 | **CSP-001 §1** | 建議基線「Java 21 + Spring Boot **3.2**」 | **Spring Boot 4.1.1** | Spring Boot 3.x 全線已 EOL；最後之 3.5 於 2026-06-30 終止 OSS 支援。3.2 更早於 2024-12-31 EOL |
| 2 | **CSP-002 §3** | 服務骨架路徑 `src/main/**kotlin**/com/beam/cardcore/...` | `src/main/**java**/com/beam/cardcore/...` | §2.1 決議採 Java 21；與 CSP-001 §5 之 Java 工具鏈一致 |
| 3 | **SAD-003 §1 DP-01、§3** | 「每個服務擁有自己的 **PostgreSQL** schema」、資料庫部署圖僅 PostgreSQL 讀寫叢集 | Oracle 為權威 OLTP；PostgreSQL 承擔 CQRS 讀模型／事件流存檔／報表 | 客戶指定 Oracle 為主（§2.4） |
| 4 | **SAD-006 §3** | 資料層僅列 PostgreSQL Primary/Replica、Redis、Kafka | 增列 **Oracle** 為權威 OLTP 層 | 同上 |
| 5 | **SDD-001、SDD-003** | ER 圖與 SQL 範例為 PostgreSQL 語法（如 `now()`、`timestamptz`、`serial`） | 補 Oracle 方言對照（`SYSTIMESTAMP`、`TIMESTAMP WITH TIME ZONE`、sequence/identity） | 同上 |
| 6 | **CSP-001 §5** | 提及自製 `validate_sql.py` | `tools/` 下**無此檔案**。須補實作，或自本表移除該列 | 實地盤點結果 |
| 7 | **proposal_v2/outline.md L48** | 「NFR **878** 項符合度統計」 | 「NFR **874** 項」 | BRD-003 載明 874 項且為單一真實來源；README 亦寫 874 |
| 8 | **GOV-001 §3.2** | 文件類型代碼表未含 `CSP-006` 之增列說明 | 無需新增 TYPE；`CSP` 序號 006 為 001–005 之連續延伸，符合 §3.1「同一 TYPE 內連續、不跳號」 | GOV-001 §3.1 |

> 第 1–5 項為**架構層級變更**，須依 GOV-003 之品質關卡重新審查並於文件「版本」欄位升版
> （1.0 → 1.1），並在修訂記錄載明變更原因。

---

## 7. 變更控制

1. 本文件之變更須經 **SA + Tech Lead + PMO** 三方同意。
2. 版本升級或技術棧替換，須以 **ADR（Architecture Decision Record）** 記錄：
   背景、候選方案、決策、後果、替代時機。
3. 變更後須同步更新 §6 之回改清單，並確認下游文件已回改。
4. 每次變更須於下方修訂記錄登載。

### 修訂記錄

| 版本 | 日期 | 修訂者 | 說明 |
|---|---|---|---|
| 1.0 | 2026-09-18 | SA / Tech Lead | 初版。依客戶拍板之技術棧（Spring Boot 4.1、Java 21、Oracle 為主、SAGA）建立基線，並提出 §6 之 8 項文件回改清單 |

---

## 附錄 A　本地環境快速上手

```bash
# 1. 安裝工具鏈（首次）
bash cardcore/ops/local/install-toolchain.sh

# 2. 切換至 Java 21
source cardcore/ops/local/env.sh

# 3. 啟動輕量核心
cd cardcore/ops/local && cp -n .env.example .env && docker compose up -d

# 4. 需要 Oracle 時
docker login container-registry.oracle.com
docker compose --profile oracle --profile tools up -d

# 5. 需要可觀測性時（Grafana: http://localhost:3000）
docker compose --profile obs up -d
```

| 端點 | 位址 |
|---|---|
| Grafana | http://localhost:3000 （admin / admin） |
| Jaeger | http://localhost:16686 |
| Prometheus | http://localhost:9090 |
| Kafka UI | http://localhost:8090 |
| Keycloak | http://localhost:8180 （admin / admin） |
| Oracle | `localhost:1521/FREEPDB1`（Free 映像之 PDB 固定為 `FREEPDB1`，見 SDD-001 §1.3 P-1） |
| PostgreSQL | `localhost:5432` / db `cardcore` |
| Kafka（主機端） | `localhost:9094` |
