# CR-010　服務間呼叫**未帶授權標頭** → 啟用認證後全數 401（含批次靜默零產出）

| 項目 | 內容 |
|---|---|
| 編號 | CR-010 |
| 版本 | **1.3（2026-09-20 核定、執行完成並實測通過；2026-09-21 補齊 gateway／bff 組態與 issuer-uri）** |
| 狀態 | ✅ **已核定／已執行並實測通過** |
| 提出日期 | 2026-09-20 |
| 核定日期 | 2026-09-20（BEAM PMO：採 **A**——**Keycloak 服務帳號（`client_credentials`）**） |
| 提出者 | WorkBuddy AI |
| 影響文件 | `SCS-022`（新增 §7.9）、`SAD-005 §3.2` 對照、`CSP-001 §8.5`、`common-spring` 組態、`ops/local/.env.example` |
| 關聯 | `HANDOVER #C-41`／`#C-42`、#A-61（**於帳單環境重建時實測發現**）、`SCS-022 §10 #7`（mTLS 未實作） |
| 嚴重度 | 🔴 **P0（功能全面不可用）**——只要認證一開，**所有**服務間呼叫全數失敗；且批次**靜默**回成功 |

---

## 1. 背景

`CR-009` 修復後重跑帳單環境時，批次回 `GENERATED` 但 **`statementCount=0`**
（「正常完成」卻什麼都沒做）。查日誌可見：

```
計息失敗，跳過該帳戶不產生帳單：account=018f2a3b-…-0010 cycle=M01 reason=計息失敗：HTTP 401
```

追查後確認：`statement → posting`、`posting → pricing` **全部 401**。
根因不在計息、不在 `CR-009`，而在**服務間 HTTP 客戶端從未帶授權標頭**。

---

## 2. 實測證據（2026-09-20）

### 2.1 服務間呼叫全數 401

於 `CARDCORE_SECURITY_ENABLED=true` 下執行帳單週期：

| 呼叫 | 結果 |
|---|---|
| `statement-service`(8092) → `posting-service`(8087) | **HTTP 401** |
| `posting-service`(8087) → `pricing-service`(8086) | **HTTP 401**（`posting` 轉為 503 `CC-PLT-5005`） |

### 2.2 Keycloak 端**其實已經備妥**（本次實測，非推測）

```
POST http://localhost:8180/realms/cardcore/protocol/openid-connect/token
grant_type=client_credentials
client_id=cardcore-services
client_secret=cardcore-services-dev-secret
→ HTTP 200
  expires_in = 900
  aud        = ["cardcore-api", "account"]
  azp        = cardcore-services
  typ        = Bearer
```

`ops/local/config/keycloak-realm.json` 中 `cardcore-services` 之設定：
`publicClient: false`（confidential）、`serviceAccountsEnabled: true`、
`standardFlowEnabled: false`、protocol mapper `audience-cardcore-api`。

> **結論**：**IdP 端早就支援，缺的是程式端**——沒有任何程式去取這個 token。

### 2.3 程式端確實沒有授權處理

```java
// ServiceClientHttpClient.send（common-spring）
HttpRequest request = requestFactory.get().timeout(...).build();
return http.send(request, HttpResponse.BodyHandlers.ofString());
```

- 建構子僅收 `ServiceClientProperties`（逾時／重試），**無任何憑證來源**；
- 呼叫端（如 `PostingServiceClient.get`）僅 `.uri(...).GET()`，**無 `Authorization` 標頭**；
- `common-spring` 之 `build.gradle.kts` 亦**無** `spring-security-oauth2-client` 依賴
  （且刻意讓 spring-security 維持 `compileOnly`）。

→ 這是**端到端都沒有這個能力**，不是「忘了傳參數」。

---

## 3. 為何現在才爆

| 時期 | 現象 |
|---|---|
| 認證關閉（本地常態） | 服務間呼叫不需 token → **缺陷存在但看不出來** |
| `#C-34` 17/17 實測 | 只測「**入站**有無 401」，**未測服務間呼叫** → 缺陷未被觸及 |
| 本次以 `CARDCORE_SECURITY_ENABLED=true` 跑批次 | 服務間呼叫全數 401 → **爆發** |

→ 「認證接線完成（17/17 實測通過）」與「認證可常態啟用」**是兩件事**。
後者還需要**出站（outbound）**這一條腿。

---

## 4. 選項與建議

| # | 方案 | 做法 | 優點 | 代價／風險 |
|---:|---|---|---|---|
| **A** ⭐ | **Keycloak 服務帳號（`client_credentials`）** | 各服務以 M2M client 向 Keycloak 取 token，快取至到期前，帶於服務間呼叫 | IdP 端**已備妥**（§2.2）；不新增依賴（JDK `HttpClient` 即可）；集中於 `common-spring`，17 服務不必重工 | token 有 900s 生命週期，須處理**快取與續取**；client secret 須妥善管理（**不放 repo**） |
| B | mTLS | 雙向 TLS 驗服務身分 | `SAD-005 §3.2` 之原始要求 | 須憑證簽發／輪替機制（本機無 CA 基礎設施）；**且 §3.2 要求的是 mTLS ＋ Service Token，二者並非擇一** |
| C | 內部路徑免認證（網路邊界隔離） | 服務間端點列入 `permitAll`，僅外部入口驗證 | 最簡單、無 token 管理 | **一旦內網被穿透即全裸**；信用卡核心不可接受；且違反 `SAD-005 §3.2` |

**建議採 A**（BEAM PMO 2026-09-20 核定）。

> ⚠️ A **不排除** B：`SAD-005 §3.2` 要求「mTLS ＋ Service Token」。
> 本次補的是 **Service Token** 那一半；**mTLS 仍待基礎設施**（憑證簽發與輪替），
> 維持 `SCS-022 §10 #7` 之揭露，不因本次而宣稱已完成。

---

## 5. 設計（採 A）

### 5.1 組態（前置詞 `cardcore.security.outbound`）

| 鍵 | 預設 | 說明 |
|---|---|---|
| `enabled` | **隨 `cardcore.security.enabled`** | 出站認證與入站**同調**——入站要認證，出站就必然要帶 token |
| `token-uri` | 無（**必填**） | Keycloak token 端點 |
| `client-id` | 無（**必填**） | `cardcore-services` |
| `client-secret` | 無（**必填**） | 由環境變數 `CARDCORE_M2M_CLIENT_SECRET` 提供，**不放 repo** |
| `cache-skew` | `30s` | 提前多久視為過期（吸收時鐘誤差與網路時間） |
| `connect-timeout` / `request-timeout` | `2s` / `5s` | 取 token 之逾時（**與業務呼叫分開**，見 §5.4） |

### 5.2 類別（`platform/common-spring`）

| 類別 | 職責 |
|---|---|
| `OutboundTokenProvider`（介面） | `String authorizationHeaderValue()`——回 `Bearer xxx`；無則 `null` |
| `ClientCredentialsTokenProvider` | 向 Keycloak 取 token、**快取**（雙重檢查鎖定）、到期前 `cacheSkew` 續取；失敗**拋錯不降級** |
| `NoOpOutboundTokenProvider` | 本地顯式停用時使用；**建構即記 WARN**（使「刻意停用」可與「忘了設定」區分），`authorizationHeaderValue()` 回 `null` → 不帶標頭 |
| `OutboundTokenAutoConfiguration` | 組裝 bean；**入站啟用卻未設出站憑證 → 啟動期失敗**（fail-closed）。於 `AutoConfiguration.imports` 登錄 |
| `ServiceClientHttpClient` | 既有類別，本次改造：建構子增 `OutboundTokenProvider`（`requireNonNull`）；送出前帶 `Authorization`；**取不到憑證時不送出請求** |

### 5.3 fail-closed 三情境

| 情境 | 行為 |
|---|---|
| `cardcore.security.enabled=true` 且出站憑證**已**設定 | 正常取 token 並帶出 |
| `cardcore.security.enabled=true` 但出站憑證**未**設定 | **啟動期失敗**（`IllegalStateException`，訊息直接給出該設什麼） |
| `cardcore.security.enabled=false`（本地顯式停用） | 不取 token、不帶標頭，並記 **WARN**（使「刻意停用」與「忘了設定」在日誌上可區分） |

> **為何不是「取不到 token 就照發不帶標頭的請求」**：
> 那正是「失敗即放行（fail-open）」，與 `SCS-022 §7.6` 已處置之
> 「宣告啟用卻沒依賴 → 靜默放行」**同型**，屬最危險的一類缺陷。

### 5.4 逾時**不得**沿用業務呼叫之預設值

取 token 是**基礎設施呼叫**，語意與業務呼叫不同：

- 不應重試（token 端點 4xx 表憑證有誤，重試無益且拖長批次）；
- 逾時須**獨立組態**（業務呼叫為連線 2s／請求 3s；取 token 建議 2s／5s，
  因 IdP 可能冷啟動）。

> 呼應既有的 `#13` 教訓：**每接一個外部系統都要重新檢視預設值，勿沿用全域值**。

---

## 6. 影響範圍

| 產出 | 變更 |
|---|---|
| `common-spring` | 新增 `OutboundTokenProvider`／`ClientCredentialsTokenProvider`／`NoOpOutboundTokenProvider`／`OutboundTokenAutoConfiguration`；`SecurityProperties` 增巢狀 `Outbound`；`ServiceClientHttpClient` 帶標頭 |
| `ServiceClientHttpClient` | 建構子增 `OutboundTokenProvider`（`requireNonNull`；本地停用時注入的是 **`NoOpOutboundTokenProvider`**，**不是** null） |
| **19 個模組** `application-local.yml`（17 領域服務 ＋ `api-gateway`／`bff-web`） | 出站組態（`client-secret: ${CARDCORE_M2M_CLIENT_SECRET:}` **不給預設值**）。實測：`src` 下 19 份**全數**含 `outbound` 區段 |
| `tools/gen_service_skeleton.py` | `T_APP_LOCAL_YML`（17 領域服務）**與** `T_APP_LOCAL_YML_CORE`（gateway／bff）**兩個模板皆同步**加出站區段（**下次建骨架即自帶**，不會因重跑而遺漏） |
| `ops/local/env.sh` | 條件載入 `~/.config/cardcore/m2m.env`（**存在才載入**；不存在時記警告） |
| `ops/local/.env.example` | 記載 `CARDCORE_M2M_CLIENT_ID`／`CARDCORE_M2M_CLIENT_SECRET`（留空） |
| `~/.config/cardcore/m2m.env` | **repo 外、權限 600**（呼應 `#9` 教訓：憑證進過版控後，即便刪除仍留存於歷史） |
| `SCS-022` | 新增 §7.9（出站憑證）；§10 #7 更新（mTLS 仍未實作）→ 升 **1.8** |
| `SAD-005` | §3.2 新增「實作狀態對照」＋ fail-closed 說明；**併修既有結構缺陷**（§10 第 7–10 項誤置於 §11）→ 升 **1.2** |
| 測試 | `ClientCredentialsTokenProviderTest`（5 例，含**負樣本**）／`OutboundTokenGuardTest`（3 例）／`ServiceClientHttpClientTest`（+3 例） |

---

## 7. 風險揭露

**7.1 修復前之風險（已解除）**

> ⚠️ ~~修復前，認證一旦常態啟用，所有跨服務功能全面不可用~~
> （授權、入帳、計息、帳單、分期…皆仰賴服務間呼叫）
> → **已於本次修復解除**：三服務以 `CARDCORE_SECURITY_ENABLED=true` 實測，
> 帶 M2M token 後週期執行成功（`201 GENERATED`、`statementCount=1`），
> 服務日誌 **401 次數 0**（詳 §9.3）。

**7.2 修復後仍存在之風險（未解除，誠實揭露）**

| # | 風險 | 說明 |
|---|---|---|
| 1 | **僅完成權杖半邊，mTLS 仍未實作** | `SAD-005 §3.2` 設計為「mTLS ＋ Service Token」雙軌，本次僅補上 Service Token。mTLS 須基礎設施（憑證簽發／輪替／佈署）先就緒 |
| 2 | **服務對服務之端點白名單未實作** | 目前為「持有有效服務權杖即可呼叫任何端點」，未依 `SAD-002 §5` 逐端點限縮 → 與「最小權限（SP-02）」仍有落差 |
| 3 | **尚未於任何客戶環境啟用** | 本機實測通過，**未於 SIT／UAT／PROD 驗證**；真實環境之 client secret 須由**金鑰管理系統**提供 |
| 4 | `client-secret` 明文見於 `ops/local/config/keycloak-realm.json` | **既有狀況，非本次引入**。該檔為本機 Keycloak 匯入用；**真實環境**憑證一律走 `~/.config/cardcore/m2m.env`（repo 外、權限 600），不得進版控 |
| 5 | **權杖快取為每服務程序各自持有** | 目前無分散式快取；多執行個體下各持一份，IdP 負載為「執行個體數 × 續取頻率」。`expires_in=900`、提前 30s 續取，單執行個體約 15 分鐘 1 次——可接受，但**未於多執行個體實測** |

---

## 8. 核定結果

| # | 核定 | 日期 |
|---:|---|---|
| 4（方案選擇） | ✅ **採 A**——Keycloak 服務帳號（`client_credentials`） | 2026-09-20 |

---

## 9. 執行結果（真實環境實測，2026-09-20）

### 9.1 程式

| 產出 | 內容 |
|---|---|
| `security/outbound/OutboundTokenProvider` | 介面：`authorizationHeaderValue()`；無憑證時回 `null`（僅本地顯式停用） |
| `security/outbound/ClientCredentialsTokenProvider` | 表單 POST 取 token、**快取**、到期前 `cacheSkew` 續取；**失敗即拋錯不降級** |
| `security/outbound/NoOpOutboundTokenProvider` | 本地顯式停用；建構時即記 **WARN** |
| `security/outbound/OutboundTokenAutoConfiguration` | 組裝 bean ＋ **入站啟用卻缺出站憑證 → 啟動期失敗** |
| `SecurityProperties.Outbound` | 巢狀組態，`isConfigured()` 判定三項必備設定 |
| `ServiceClientHttpClient` | 送出前帶 `Authorization`；憑證來源 `requireNonNull` |
| `ServiceClientAutoConfiguration` | 以 `ObjectProvider` 惰性注入（去自動配置順序耦合） |
| 17 個服務 `application-local.yml` | 由**產生器模板**同步補上出站區段（`client-secret` 不給預設值） |
| `ops/local/env.sh`／`.env.example`／`~/.config/cardcore/m2m.env` | 憑證外置（600），「若存在才載入」 |

### 9.2 單測與判定力

`common-spring` **60 → 71 測試、0 失敗**：

| 測試類 | 例數 | 核心 |
|---|---:|---|
| `ClientCredentialsTokenProviderTest` | 5 | ①表單內容 ②**快取命中**（IdP 僅被呼叫 1 次）③到期續取 ④IdP 非 200 拋錯 ⑤缺 `expires_in` 拋錯 |
| `OutboundTokenGuardTest` | 3 | 憑證齊備→建立／**缺漏→啟動期失敗**／入站停用→NoOp |
| `ServiceClientHttpClientTest` | +3 | 帶 `Authorization`／**取不到憑證不送出請求**／null provider 拋 NPE |

★ **負樣本實測（證明非假綠）**：
- 令 `CachedToken.isUsable()` 恆回 `false` → 案例 ②（快取命中）**精準轉紅**
  （IdP 被呼叫 **3** 次而非 1 次），其餘 4 例不受影響。
- ⚠️ **第一次負樣本失敗了**：我只關掉**外層**的快取讀取，
  但雙重檢查鎖定的 `synchronized` 區塊內**還有第二處**判定，故仍命中快取、測試全綠。
  ——**若未察覺即為假綠**；改關 `isUsable()` 才真正測到。

### 9.3 真實端到端（三服務，認證啟用）

以 `CARDCORE_SECURITY_ENABLED=true` 啟動 `statement`(8092)／`posting`(8087)／`pricing`(8086)：

| 步驟 | 結果 |
|---|---|
| 三服務啟動日誌 | 皆見「服務間呼叫憑證已啟用：client_credentials（client=cardcore-services）」＋「認證機制已啟用：JWT」 |
| **無 token** 呼叫批次端點 | **401 `CC-PLT-4010`**——入站確有鎖 |
| 以 `client_credentials` 取 token | 200（長 1,307） |
| **帶 token** 執行 2026-12 週期 | **201 `GENERATED`、`processed=1`、`statementCount=1`** |
| 三服務日誌之 401 次數 | **0／0／0** |
| DB | 新增 `STMT2026120010`：`prev=71,309`（正確銜接 11 月）／`new=72,341` |

> **對比修復前**：同一操作為 `statementCount=0`，且日誌全為「計息失敗：HTTP 401」。

### 9.4 一併處理：`#C-42` 批次零產出防呆

| 情境 | 處置 |
|---|---|
| 帳戶清單為空 | 記 **WARN**（明載週期代碼與主檔可能未建） |
| 全部帳戶被跳過（`statementCount=0 && processedCount>0`） | 記 **ERROR**（明載常見成因：服務間認證失敗／IdP 不可用／posting 異常） |
| 回應 | `CycleRunView` **加值新增 `skippedCount`**（＝`processedCount − statementCount`），純加值非 breaking |

`statement-service` **19 → 22 測試、0 失敗**（新增 `StatementZeroOutputTest` 3 例）；
★ 負樣本：令 `skippedCount` 恆為 0 → 案例 ② **精準轉紅**、①③不受影響。
契約由 `tools/gen_openapi.py` 重產 → **72 端點 0 錯誤**，且 `skippedCount`
**僅**出現於 `statement-service.yaml`（未誤動其他服務）。

### 9.5 補齊 `api-gateway`／`bff-web` 之認證組態（2026-09-21）

v1.1 執行時僅套用於 **17 個領域服務**；`api-gateway` 與 `bff-web` 之
`application-local.yml` 係由另一模板 `T_APP_LOCAL_YML_CORE` 產生，
**未含 `cardcore.security` 區段** → 不受開關影響（v1.1 §6 已列為已知落差）。
本次依**組態先行對齊**原則補齊（非新設計，屬既有核定設計之套用範圍補正，故未另立 CR）。

| 項 | 處置 |
|---|---|
| `T_APP_LOCAL_YML_CORE` 模板 | 同步加入 `cardcore.security.enabled` ＋ `outbound` 區段（與領域服務版同文） |
| 兩個既有檔 | `api-gateway`／`bff-web` 之 `application-local.yml` 一併補上（產生器只寫不存在的檔，故須兩邊都動） |
| 覆蓋率實測 | `src` 下 **19/19** 份 `application-local.yml` 全數含 `outbound` 區段 |
| 模板渲染實測 | 以 `string.Template.safe_substitute` 渲染 CORE 模板 → `${CARDCORE_SECURITY_ENABLED:false}`／`${CARDCORE_M2M_CLIENT_SECRET:}`／`${KEYCLOAK_TOKEN_URI:...}` **皆完整保留**（未被當佔位符吃掉） |
| `gen_service_skeleton.py --check` | **25 模組、缺漏 0** |

**啟動實測（`api-gateway`，兩情境證明組態真被讀到且開關可區分）**：

| 情境 | 結果 |
|---|---|
| `CARDCORE_SECURITY_ENABLED=true` | **啟動失敗**（fail-closed）——`SecurityDependencyGuardAutoConfiguration` 明確拋出「classpath 上找不到 Spring Security…請加入 `spring-boot-starter-oauth2-resource-server`」。此即**組態已生效**之證（若未讀到組態，會靜默起來） |
| `CARDCORE_SECURITY_ENABLED=false` | **正常啟動**，`Netty started on port 8100`、`Started ApiGatewayApplication in 6.719s` |

> ⚠️ **揭露（非阻礙）**：`api-gateway`／`bff-web` 之 `build.gradle.kts` 目前**尚未**引入
> `spring-boot-starter-oauth2-resource-server`，故兩模組於「宣告啟用」時會以上述
> **fail-closed 方式明確失敗**，而非靜默放行——此為守門預期行為。
> 待此二模組實際承擔入站驗證職責時，須補上該依賴（屬另一項工作，未含於本次）。

### 9.6 追加：`issuer-uri` 亦須一併對齊（2026-09-21）

v1.2 只補了 `cardcore.security`，**漏了 `spring.security.oauth2.resourceserver.jwt.issuer-uri`**——
實測 17 個領域服務之 `application-local.yml` **皆有**該設定，gateway／bff **沒有**。
依 `SCS-022 §7.7 ②` 之既有慣例「**含尚未導入依賴者，預先就位**」，二者應比照補上。

| 項 | 處置 |
|---|---|
| `T_APP_LOCAL_YML_CORE` 模板 | 加入 `spring.security.oauth2.resourceserver.jwt.issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8180/realms/cardcore}` |
| 兩個既有檔 | `api-gateway`／`bff-web` 同步補上 |
| 覆蓋率實測 | **19/19** 份 `src` 組態皆含 `issuer-uri` |
| 模板比對實測 | 兩模板（CORE／領域服務）**去註解後實質 key 完全一致**；差異僅為註解文字與 CORE 不應有之 `datasource`／`redis`／`kafka`／`jpa`（無資料庫模組） |
| 啟動實測 | 重新 `bootJar` 後以 `CARDCORE_SECURITY_ENABLED=false` 啟動 → **正常**（`Netty started on port 8100`、`Started ApiGatewayApplication in 7.915s`）→ 新增區段**無副作用** |

### 9.7 ★ 根因：`SCS-022` 之**錯誤陳述**使二模組被排除（2026-09-21 發現並更正）

補齊過程中追出**為什麼** v1.1 會漏掉這兩個模組——不是疏忽，是**文件寫錯**：

| 出處 | 原記載 | 實測 |
|---|---|---|
| `SCS-022 §3` | 「`backend/services/` 下僅有 17 個領域服務，**並無 gateway／bff 模組**」 | ❌ **錯誤**。二模組**確實存在**，位於 **`backend/platform/api-gateway`** 與 **`backend/platform/bff-web`** |
| `SCS-022 §2` 適用範圍 | 「全部 17 個領域服務」 | 因而**排除** gateway／bff |

→ 連鎖結果：`CR-010` v1.1 依規格只套用 17 個領域服務，二模組**未被納入**。
錯誤陳述若不更正，後續任何「全部服務」類工作都會**重複漏掉同一批模組**。

**已更正**：`SCS-022` 升 **1.9**——§3 更正並補列二模組狀態表、§2 適用範圍納入、
§7.7 步驟 ②／③ 改為 19/19、新增「gateway／bff 導入狀態」表。

### 9.8 非本地環境（`application.yml`）**刻意不設** `cardcore.security`——係設計，非缺漏

實測 19 份 `application.yml`（非本地主組態）**皆無** `cardcore.security` 區段。
查證 `SCS-022 §7.4`：「停用只能**顯式**宣告，且**僅限本地檔**」→
非本地即以**預設 true（fail-closed）** 運作，憑證由環境變數（KMS）注入。

> ⚠️ **此點一度被誤判為「各服務組態之落差」而準備補上**——若未先查證規格即動手，
> 將把**正確的 fail-closed 設計改掉**（在非本地檔寫入 `enabled`，或硬加區段）。
> 呼應既有教訓 **#17「未查證之事不得寫成待辦」**：此為**幽靈落差**，已撤銷、未動手。

---

## 10. 修訂記錄

| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-09-20 | 初版。帳單環境重建時實測發現服務間呼叫全數 401；經查 IdP 端（Keycloak `cardcore-services`）**早已備妥**，缺口全在程式端。提列三方案並建議採 A；BEAM PMO 核定採 A |
| 1.1 | 2026-09-20 | 執行完成並實機實測：①`common-spring` **60 → 71 測試 0 失敗**（`ClientCredentialsTokenProviderTest` 5／`OutboundTokenGuardTest` 3／`ServiceClientHttpClientTest` +3）；★ **負樣本第一次失敗**（只關外層快取讀取，未察 `synchronized` 內還有第二處判定 → 差點變假綠），改關 `isUsable()` 後案例 ② 精準轉紅；②17 服務 `application-local.yml` 由**產生器模板**同步補出站區段（`--force` 會覆寫骨架，**未執行**）；③憑證外置 `~/.config/cardcore/m2m.env`（600）；④**真實三服務端到端**（認證啟用）：無 token 401、帶 token 執行 2026-12 週期 **`statementCount=1`**、三服務 401 次數 **0**、新增 `STMT2026120010`（`prev=71,309`／`new=72,341`）；⑤一併完成 **`#C-42`** 零產出防呆（`skippedCount` 加值、WARN／ERROR 日誌），`statement-service` **19 → 22 測試 0 失敗**，契約重產 72 端點 0 錯誤且僅動 `statement-service.yaml`；⑥`SCS-022` 升 **1.8**（新增 §7.9、更新 §10 #7：mTLS **仍未實作**） |
| 1.2 | 2026-09-21 | **補齊 `api-gateway`／`bff-web` 之認證組態**（v1.1 §6 所列之已知落差）：依「**組態先行對齊**」原則，將 `cardcore.security`（含 `outbound`）同步至 `T_APP_LOCAL_YML_CORE` 模板**與**兩個既有檔——產生器只寫不存在的檔，故**模板＋既有檔兩邊都動**。覆蓋率實測 **19/19**；模板渲染實測 `${...}` 佔位**未被 `string.Template` 吃掉**；`--check` 25 模組缺漏 0。**啟動實測（`api-gateway`）兩情境**：`=true` → **fail-closed 啟動失敗**並明指缺 `oauth2-resource-server`（此即組態**已生效**之證，未讀到者會靜默起來）；`=false` → 正常啟動（`Netty started on port 8100`）。⚠️ 揭露：此二模組尚未引入 `oauth2-resource-server`，承擔入站驗證職責前須補依賴（另一項工作）。本次**未另立 CR**——非新設計，屬既有核定設計之**套用範圍補正** |
| 1.3 | 2026-09-21 | **追加 §9.6**：v1.2 只補 `cardcore.security`，**漏了 `spring.security.oauth2.resourceserver.jwt.issuer-uri`**（實測 17 領域服務皆有、gateway／bff 無）→ 依 `SCS-022 §7.7 ②`「含尚未導入依賴者，預先就位」慣例補齊；模板＋既有檔兩邊都動；覆蓋率 **19/19**；兩模板去註解後**實質 key 完全一致**；重新 `bootJar` 後以 `=false` 啟動**正常**（8100）→ 無副作用。**§9.7 追出根因**：`SCS-022 §3` 原寫「並無 gateway／bff 模組」**係錯誤陳述**（二模組實位於 `backend/platform/`），連同 §2 適用範圍使二者被排除於認證規格外→ 連鎖導致 v1.1 僅套用 17 個；已於 `SCS-022` **1.9** 更正。**§9.8 撤銷一項幽靈落差**：19 份 `application.yml`（非本地）無 `cardcore.security` **係設計如此**（§7.4 停用僅限本地檔、非本地走預設 fail-closed），一度誤判為落差，查證後**未動手** |
