# CR-035　POC-8 剩餘四項之設計與裁定——8-2 權限參數化、8-3/8-5 授權端扣抵、8-6/8-7 臨時額度串動、`scope_ref`

| 項目 | 內容 |
|---|---|
| 文件編號 | CR-035 |
| 版本 | **1.1**（✅ **Phase 1／2／3 全數完成**——8-3／8-5、8-2、8-6／8-7 皆已實作並真機驗證。★ **更正 §2.4 之「繳款截止日」來源**：原載讀 `CC_STMT` 為**跨 schema**、**不可行**，已改為推算並標記 `DERIVED`。★ **§2.3 之折衷已處理**：角色改由**已驗證之權杖**決定（**折衷移除**）；客層保留但附四項證據。前版 **1.0**） |
| 狀態 | ✅ **已裁定並全數執行完畢**（Phase 1／2／3 皆已實作並真機驗證） |
| 提出日 | 2026-10-02 |
| 提出者 | WorkBuddy AI（開發團隊） |
| 緣起 | BEAM PMO：「**請接續完成 8-2 角色／客層上限、8-3/8-5 授權端扣抵與超額拒絕（屬 authorization-service，在此之前專用額度尚不能實際動用）及 8-6/8-7 臨時額度串動、`scope_ref`（MCC）**」 |
| 前案 | `CR-014 v1.1`（`#A-153`）——專用額度資料模型已落地；本 CR 為其 §8 已知限制之收斂 |
| 關聯 | `BRD-004 POC-8`、`BRD-001 §5.4 BF-04`（`BR-04-01`～`BR-04-04`）、`SDD-001 §3.4`、`SDD-005`、`SDD-006`、`SCS-001`、`SCS-002`、`GOV-006 2.2.3b` |
| 嚴重度 | 🟡 中——**在此之前專用額度「可設定但不能動用」**（POC-8 之核心價值未兌現） |

---

## 1. 動工前事實查證（真機；`cardcore-preflight-fact-check`）

| 查證項 | 結果 |
|---|---|
| `CC_LIM.SPECIAL_LIMITS` 欄位 | 12 欄（含 `currency`）；**無** `scope_ref` → 需新增 |
| `CC_LIM.LIMIT_HOLDS` 欄位 | 11 欄；**無** `special_limit_id` → 需新增。`hold_type IN ('GENERAL','SPECIAL','INSTALLMENT')`——**`SPECIAL` 值早已在值域內**（既有設計已預留，惟從未使用） |
| 「客層」來源 | ★ **全庫無 `SEGMENT` 欄位**。最接近者為 `CC_PRODUCT.PRODUCTS.CARD_GRADE`（卡等）與 `CC_RW.POINTS_TIERS.TIER_CODE`（忠誠層級） |
| 「角色」來源 | ★ **不存在**——`LimitAdjustmentCommand` 無角色欄位；平台亦無 RBAC 資料源 |
| `posting.transaction.posted.v1` payload | `postingId`／`txNo`／`accountId`／`txType`／`amount`／`currency`／`postingDate`／`subAccountEntries[]`——★ **不含 `authorizationId`**（8-6 需要它才能識別「哪一筆用了專用額度」）。惟 `CC_POSTING.TRANSACTIONS` **有** `AUTHORIZATION_ID` 欄位 → 可作為**可選欄位**補入（`SAD-004 §5.3` 不升版） |
| 繳款截止日來源 | `CC_STMT.STATEMENTS.payment_due_date`（`NOT NULL`）、`CC_POSTING.BALANCES.payment_due_date`（可空）；`StatementGenerationService` 以 `帳單日 + 25 天` 產生 |
| 授權服務之額度讀模型 | `account_limit_caches`（`total_amount`／`used_amount`）＋ `limit_holds`——**無任何專用額度欄位** |
| ACL | `limit-service` 接受 `cc-authorization-service` ✓；`product-service` 接受 `cc-limit-service` ✓；★ **`card-service` 只接受 `cc-authorization-service`** → **`limit-service` 不得呼叫 `card-service`**（影響 8-2 之客層自動取得） |

---

## 2. 逐項設計與裁定

### 2.1 `scope_ref`（用途範圍，MCC）——POC-8 之 8-3 前置

**裁定**：`SPECIAL_LIMITS` 新增 `scope_ref VARCHAR2(64 CHAR)`（`V7`）。

| 值 | 語意 |
|---|---|
| `NULL`／空白 | **不限定**——該用途之**任何** MCC 皆可動用 |
| 非空 | **逗號分隔之 MCC 白名單**（如 `9311,9399`）；交易之 MCC 須**恰在其中** |

**理由**：`BRD-001 §5.4 BF-04` 之措辭為「**依用途設定**（繳稅／保費／**特定 MCC**）」——
**用途為主軸、MCC 為細化**。故「不限定」是合法設定，非缺漏。

**多筆命中時之選擇順序**（決定性，避免不確定行為）：
1. `scope_ref` **非空且命中** 者優先（較特定）；
2. 其次為 `scope_ref` 為 `NULL`（不限定）者；
3. 同級則依 `purpose` 升冪、`id` 升冪（**保證決定性**）。

> ⚠️ **繳稅之 MCC 慣例**：`9311`（Tax Payments）／`9399`（Government Services, NEC）。
> 種子資料將以 `scope_ref='9311,9399'` 設定 TAX 專用額度。

---

### 2.2 8-3／8-5：授權端扣抵與超額拒絕（**本 CR 之核心**）

**現況**：`AuthorizeUseCase` 只查 `limitPort.availableLimit(accountId, currency)`（**卡人額度**），
專用額度**完全不參與**——故 8-3 之「不佔卡人額度」與 8-5 之「超額拒絕」**皆無法成立**。

**裁定：三段式池選擇（Pool Selection）**，於 `AuthorizeUseCase` 內實作：

```
① 查「適用之專用額度」（account, currency, mcc）→ 得 (specialLimitId, 可用專用額度)
② checkLimit：
     若 ① 存在 且 可用專用額度 ≥ 交易金額  → PASS（pool = SPECIAL）
     否則若 卡人可用額度 ≥ 交易金額        → PASS（pool = CARDHOLDER，8-4）
     否則                                  → FAIL（CC-LIM-1001，8-5）
③ hold：pool = SPECIAL → holdType='SPECIAL' + specialLimitId
         pool = CARDHOLDER → holdType='GENERAL'
```

**★ 關鍵裁定（避免「雙重佔用」）**：**使用專用額度時，`LIMIT_HOLDS` 只記一筆（`hold_type='SPECIAL'`），
且**不動** `account_limits` 之任何欄位**。故「不佔卡人額度」（`BR-04-01`）是**結構性成立**，
不是靠事後沖銷——下游之 `available = total − used − holds` 天然不含該筆（見 `SCS-001 §7.15` 之既有設計）。

**專用額度池之佔用**：`special_limits.used_amount` 於 **hold 時 `+=`**、**release 時 `-=`**（下限 0，同一交易）。
→ 與卡人額度之 `used_amount`＋`limit_holds` 雙層語意**刻意不同**：
專用額度之「已用」＝**佔用中**（授權已佔、尚未釋放），
因專用額度無「請款比對」之獨立訊號可依（見 §3 已知限制）。

**Stand-in 降級**：limit-service 不可用時，`StandInLimitPort` 之本地讀模型**沒有專用額度資料**
→ **查無適用之專用額度** → 自然退回卡人額度（8-4）。此為**安全降級**（不會超額放行專用池）。
⚠️ 如實揭露：Stand-in 期間專用額度**不可用**（非「假裝有」）。

**新增端點**（`limit-service`）：
`GET /api/v1/accounts/{accountId}/special-limits/applicable?currency=&mcc=&amount=`
→ 回 `SpecialLimitApplicabilityView { applicable, specialLimitId, purpose, scopeRef, availableAmountMinor, reason }`
（**`applicable=false` 時亦回 200**——「無適用」是正常結果，非錯誤；`reason` 說明未命中原因。）

---

### 2.3 8-2：角色／客層上限（參數化權限）

**裁定**：新增 `CC_LIM.SPECIAL_LIMIT_POLICIES`（`V8`）。

| 欄位 | 說明 |
|---|---|
| `id` | PK |
| `role_code` | 角色（如 `cardcore-csr`／`cc-payment-service`／`UNVERIFIED`） |
| `segment_code` | 客層；**`*` ＝ 不限** |
| `currency` | 幣別 |
| `max_amount` | 該組合之**可設定上限**（分） |
| `status` | `ACTIVE`／`INACTIVE` |
| `created_at`／`updated_at`／`version` | SQL-05 通用欄位 |

`UNIQUE (role_code, segment_code, currency)`；索引 `(role_code, status)`。

#### 2.3.1 ★★ 折衷之處理（本 CR 之重點之一）

初版設計把「角色」與「客層」**兩者**都推給「請求帶入」，並承認那是折衷。
**動工前查證後，兩者之處置不同**：

##### ① 角色：**折衷移除**——改由「已驗證之權杖」決定（伺服器端）

**證據**：`common-spring` **已有** `JwtRoleConverter`（`SCS-022 §7.2`）——
將 Keycloak 之 `realm_access.roles` 抽為 `ROLE_<name>` authorities，
且 `SecurityAutoConfiguration` 已將其裝入 `JwtAuthenticationConverter`。

**裁定**：`role_code` **一律由伺服器自 `Authentication` 決定，不接受請求帶入**（**防提權**）。
來源優先序（**皆為已驗證權杖之事實**，或**明確之未驗證狀態**）：

| 序 | 條件 | `role_code` | `verified` |
|---|---|---|---|
| 1 | 有 `ROLE_*` authorities（使用者權杖之 realm roles） | 去 `ROLE_` 前綴；**多筆取字典序最小**（**決定性**） | `true` |
| 2 | 無 realm roles 但為服務權杖（`JwtAuthenticationToken` 之 `azp` 存在） | `azp` 值（如 `cc-payment-service`） | `true` |
| 3 | 以上皆無（**認證停用**／匿名） | **`UNVERIFIED`** | `false`（**併記 WARN**） |

★ **為何「取字典序最小」而非「取最寬鬆」**：多角色使用者之語意本就不明；
取最小者**決定性**且**不會因角色組合而放寬**，並於軌跡記錄「實際使用之角色」。
★ **序 3 為 fail-closed 之樞紐**：認證停用時角色為 `UNVERIFIED`，
**必須顯式設定 `role_code='UNVERIFIED'` 之政策才會通過**——不會「因為沒認證所以放行」。

##### ② 客層：**折衷保留，但附證據、防護與後續**

**為何無法自動取得（真機查證）**：權威來源為 `CC_PRODUCT.PRODUCTS.CARD_GRADE`，
惟取得路徑為「**歸戶 → 卡片 → 產品 → 卡等**」，而——

| # | 障礙 | 證據 |
|---|---|---|
| 1 | `CC_LIM` **無任何產品／卡等欄位** | `all_tab_columns` 實查 |
| 2 | `card-service` 之 ACL **只接受 `cc-authorization-service`** | `cardcore-service-acl.yml` |
| 3 | `party-service` 之 ACL **不含 `cc-limit-service`** | 同上 |
| 4 | `CC_PARTY.ACCOUNTS` **無客層欄位** | 實查（且全庫無 `SEGMENT`／`LEVEL`） |

→ 自動取得需**同時**開放兩個 ACL ＋ 補讀模型，**與 8-2 之價值不成比例**。

**裁定**：`segment_code` **由請求帶入（可選，預設 `*`）**，並——
1. **定位為「政策查詢鍵」而非「授權判斷」**（真正的權限在角色，角色已不可偽造）；
2. **程式與文件明確標註**其未經權威來源驗證；
3. **列 WBS 後續**：若客戶要求自動取得，需三項變更（見 §3）。

#### 2.3.2 政策查詢與檢核

**檢核時點**：`LimitAdjustmentService.apply()`，`adjustType='SPECIAL'` 時——
`afterAmount ≤ 適用政策之 max_amount`。

**查詢優先序**（**決定性**）：`(role, segment)` → `(role, '*')` → **皆無 → 拒絕**
（**fail-closed**，`CC-LIM-1002`「超過可設定上限」）。

> ⚠️ **不設 `('*','*')` 萬用列**：那會讓「忘了設定政策」與「刻意全域放行」無法區分。
> 需要全域上限者，明確列出各角色即可。

#### 2.3.3 政策之維護（`8-1` 之「依人員權限控管」）

新增 2 個端點：`GET /api/v1/special-limit-policies`（查詢）與
`PUT /api/v1/special-limit-policies/{roleCode}/{segmentCode}`（upsert；冪等）。
★ **停用** ＝ `PUT` 帶 `status=INACTIVE`（**不做 DELETE**——政策為稽核相關設定，宜留痕）。
⚠️ **端點本身之權限**：目前僅受 `limit-service` 之一般認證保護（**任何已驗證呼叫者皆可設定**）——
**正式應限定特定角色**；平台**無 RBAC 資料源**可資判斷，故**如實揭露**並列後續（見 §3）。


---

### 2.4 8-6／8-7：臨時額度串動

**裁定**：以**事件驅動**，於 `limit-service` 新增兩個消費端。

#### 8-6（綜所稅請款 → 調高卡人臨時額度）

- **訂閱**：`cc.posting.transaction.posted`（`SAD-002 §3.2` 早已載明 `limit` 為消費者，**惟從未實作**）。
- **前置（契約缺口）**：現行 payload **不含 `authorizationId`** → 無法識別「哪一筆用了專用額度」。
  → **補為可選欄位**（`SAD-004 §5.3`：新增**可選**欄位**不升版**）。來源已存在
  （`CC_POSTING.TRANSACTIONS.AUTHORIZATION_ID`），**不需臆造**。
- **處理**：依 `authorizationId` 查本服務之 `LIMIT_HOLDS` → 若 `hold_type='SPECIAL'`：
  1. 將該筆佔用**釋放**（請款比對，`BR-03-02`）→ 專用池 `used_amount -= 金額`；
  2. **調高臨時額度**：`temporary_amount += 金額`，`temp_start = 入帳日`，
     `temp_end = 繳款截止日 + 7 日`。
  3. 寫入**串動紀錄**（見下）以供 8-7 對應。
- **「繳款截止日」之來源**：★ **本節原設計有誤，已於實作時更正**——
  原載「以 `CC_STMT.STATEMENTS.payment_due_date` 之最近一期為準」，
  惟 **`limit-service` 之 schema 為 `CC_LIM`，讀 `CC_STMT` 屬跨 schema**
  （`SAD-003`／`SQL-07` 禁止）→ **不可行**。
  → 改以「**入帳日 ＋ 帳單週期（預設 25）＋ 7 日**」**推算**，
  並於紀錄標記 `due_date_source = DERIVED`——**不臆造為「帳單之繳款截止日」**。
  可配置：`cardcore.limit.special-temp-raise.statement-cycle-days`／`.grace-days`。
  **精確值需 `statement` 整合**（或於 `posted.v1` 補 `paymentDueDate`）→ 列後續。

#### 8-7（繳款 → 調降卡人臨時額度）

- **訂閱**：`cc.payment.payment.posted`（本服務**已訂閱**，見 `SCS-002 §7.7`）→ **併入既有消費者**。
- **處理**：依「該歸戶尚未回收之 8-6 串動紀錄」，依**先進先出**回收
  `min(繳款金額, 未回收總額)` → `temporary_amount -= 回收額`（下限 0），
  並將對應紀錄標記 `recovered_at`。
- ★ **不得**直接將 `temporary_amount` 歸零——該欄可能同時承載**人工設定之臨時額度**（`§7.2` 之申請）。
  故**必須**以串動紀錄精確扣回，否則會誤砍客戶經核准之臨時額度。

#### 新增表 `CC_LIM.SPECIAL_LIMIT_TEMP_ADJUSTMENTS`（8-6/8-7 之串動紀錄）

| 欄位 | 說明 |
|---|---|
| `id` | PK |
| `account_id`／`currency` | 歸戶 × 幣別 |
| `special_limit_id` | 來源專用額度 |
| `authorization_id`／`posting_id` | 來源（可追溯） |
| `raised_amount` | 8-6 調高之金額 |
| `recovered_amount` | 8-7 已回收金額（累計） |
| `raised_on`／`raise_until` | 調高日／迄日（＝繳款截止日 + 7） |
| `due_date_source` | `STATEMENT`／`DERIVED` |
| `status` | `OPEN`／`CLOSED` |
| `created_at`／`updated_at`／`version` | SQL-05 通用欄位 |

---

## 3. 分階段執行（每階段皆為「完整可驗證之交付單元」）

| 階段 | 範圍 | 內容 | 驗收 |
|---|---|---|---|
| **Phase 1** | `scope_ref` ＋ **8-3／8-5** | ✅ **已完成（2026-10-02，`#A-154`）**：`V7__special_limit_pool.sql`（`special_limits.scope_ref`＋`limit_adjustments.scope_ref`＋`limit_holds.special_limit_id`）；`SpecialLimitRepository#findApplicable`／`increaseUsed`／`decreaseUsed`；`LimitApplicationService` 之 SPECIAL 池路徑與釋放回沖；新端點 `special-limits/applicable`；`authorization-service` 之 `SpecialLimitPort`＋三段式池選擇；契約；單測；**真機 11 項** | ✅ 專用額度交易**不佔卡人額度**（`account_limits` 全程未變、`availableLimit` 不因專用佔用而減）；池不足 → **回退**卡人額度；兩者皆不足 → **DECLINED 51** |
| **Phase 2** | **8-2** | `V8`（`SPECIAL_LIMIT_POLICIES`）；`CallerIdentityPort`（★ **角色自權杖**）；`apply()` 政策檢核；政策 `GET`／`PUT` 端點；契約；單測；**真機** | 不同角色之可設定上限**確實生效**；查無政策 → 拒絕；★ **請求帶入之角色不得提權** |
| **Phase 3** | **8-6／8-7** | ✅ **已完成（2026-10-02，`#A-156`）**：`posting.transaction.posted.v1` 補**可選** `authorizationId`（**不升版**）；`V9`（串動紀錄表）；`PostingTransactionPostedListener`（8-6）＋ 繳款消費端併入 8-7（**窄埠**）；`raiseTemporary`／`lowerTemporary`（★ **相對增量**）；契約；單測；**真機 6 項**（真實 Kafka ＋ Oracle） | ✅ 請款 → 臨時額度 **0 → 15000**、迄日 **2026-11-03**（入帳日＋32）；繳款 10,000 → **5000**、再繳 5,000 → **0** 且紀錄 `CLOSED`；★ 專用池 `used_amount` 對稱回沖 **15000 → 0** |

**共用**：**一階段一遷移**（`V7`／`V8`／`V9`）——
★ **不預先建立「有表無程式」的空表**（避免重蹈 `CR-014 §0`「表已存在、程式全無」之覆轍）。
各階段之實作與驗證**獨立**，可分別交付。

---

## 4. 契約變更（破壞性與非破壞性）

| 契約 | 變更 | 性質 |
|---|---|---|
| `limit-service.yaml` `HoldRequest` | 新增可選 `specialLimitId` | 非破壞（新增可選） |
| `limit-service.yaml` | 新增 `GET /accounts/{accountId}/special-limits/applicable` ＋ `SpecialLimitApplicabilityView` | 非破壞（新增端點） |
| `limit-service.yaml` `SpecialLimitView` | 新增 `scopeRef` | 非破壞（新增可選） |
| `limit-service.yaml` `LimitAdjustmentRequest` | 新增可選 `applicantRole`／`applicantSegment` | 非破壞（新增可選） |
| `posting-service.yaml` 事件 payload | `posted.v1` 補可選 `authorizationId` | 非破壞（`SAD-004 §5.3`） |

> 依 `CR-030` P4：**全部為非破壞性**（新增可選欄位／新增端點），**不升版**。

---

## 5. 已知限制（誠實揭露）

1. ~~**`role_code`／`segment_code` 未經身分系統驗證**~~ → **部分解除（2026-10-02）**：
   ★ **`role_code` 已改由「已驗證之權杖」決定**（`JwtRoleConverter` 之 realm roles／服務權杖之 `azp`），
   **不接受請求帶入** → **折衷移除**。⚠️ **`segment_code` 仍由請求帶入**（附證據，見 §2.3.1 ②）。
2. **客層未自動取得**（`limit-service` 不得呼叫 `card-service`／`party-service`；`CC_LIM` 無產品欄位）
   ——須由呼叫端帶入。**後續**：需三項變更（`SAD-002 §5` 開放 `cc-limit-service` → `card`／`party`；
   `party` 提供歸戶→卡片；`card`／`product` 提供卡等）。
3. ★ **政策維護端點之權限未收斂**（`§2.3.3`）：目前任何已驗證呼叫者皆可設定政策上限——
   正式應限定特定角色，惟平台**無 RBAC 資料源**可資判斷 → **如實揭露**，列後續。
4. **專用額度之「已用」語意為「佔用中」**，非「已入帳未清償」：
   因專用池之請款比對訊號即為「釋放」（Phase 3 實作後，請款會釋放佔用並改以臨時額度表達）。
   Phase 1～2 期間，請款不會釋放專用佔用（**尚無消費者**）→ 專用池之佔用**只增不減**，
   須以人工或後續 Phase 3 收斂。
5. **`scope_ref` 為 MCC 白名單**，不支援萬用字元／範圍（如 `93**`）。
6. **Stand-in 期間專用額度不可用**（本地讀模型無此資料）→ 一律退回卡人額度。

---

## 6. 修訂記錄

| 版本 | 日期 | 修訂內容 |
|---|---|---|
| 1.1 | 2026-10-02 | ✅ **Phase 1／2／3 全數完成**。**Phase 1（`#A-154`）**：`scope_ref` ＋ 8-3／8-5（三段式池選擇；★ `BR-04-01` **結構性成立**）。**Phase 2（`#A-155`）**：8-2（政策表 ＋ ★ **角色自權杖**——**折衷移除**；客層保留但附**四項證據**）。**Phase 3（`#A-156`）**：8-6／8-7（串動紀錄 ＋ `posted.v1` 補可選 `authorizationId` ＋ FIFO 回收）。★ **更正 §2.4**：「繳款截止日」原設計讀 `CC_STMT` 為**跨 schema**、**不可行** → 改為推算並標記 `DERIVED`。★ **併載真機揭露之兩項陷阱**：`all_tab_columns` 跨 schema 掃描靜默回 0 列；Keycloak 權杖之**雜訊角色**（`default-roles-*`／`offline_access`）會使角色解析**荒謬化** |
| 1.0 | 2026-10-02 | 初版：動工前真機查證（8 項）、四項設計與裁定（`scope_ref` 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表）、契約變更（全為非破壞）、三階段執行計畫、五項已知限制 |
| 1.0 | 2026-10-02 | 初版：動工前真機查證（8 項）、四項設計與裁定（`scope_ref` 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表）、契約變更（全為非破壞）、三階段執行計畫、五項已知限制 |
