# SCS-002　額度服務（limit-service）程式設計規格

| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-002 |
| 版本 | **1.20**（`CR-055` P4-2／P4-3：**新增 §7.18 預借現金額度**（上限＝總額度×ratio、可用＝min(上限−已預借, 有效可用)；`null`≠`0` fail-closed；★ `V16` 擴充 `hold_type` 值域——真機攔下 `ORA-02290` 之真實缺口）＋ **§7.19 企業子額度與組織樹**（`V17`；上層可用扣減**所有下層佔用**；檢核走**上層鏈**並指出不足之層）。前版 **1.19**（`CR-055` P4-1：**新增 §7.17 共享池權利與卡片上限檢核**——三概念分離（總額度／卡片名目額度／共享池權利）、可刷上限公式、查詢分開呈現；★ 修正「不享共享池之卡可刷到總額度」之超放缺陷。前版 **1.18**（`CR-054` P1：§7.6 佔用事件 payload 補**可選** `instrumentId`——權威表早有值、事件原本不帶（「權威表有、事件沒有」），補此欄為投影唯一來源；`released` 由同交易 `findHoldInstrumentId` 回查。前版 **1.17**（`CR-052` C-3：**新增 §7.16 卡片級額度調額寫入**——`V12` 調額單維度化／帶 `instrumentId` 者只改 CARD 列／無 CARD 列時核准後建立（客戶提供初始值之唯一途徑）／★ **卡片級不發** `limit.limit.adjusted.v1`（避免覆蓋帳戶總額度）。前版 **1.16**（`CR-053` D-1：**新增 §7.15 載體（卡）級主額度**——額度主體維度化 `V11`／`subject_type`＋`subject_id`／trigger 補值／唯一鍵改主體界定；契約 `GET /instruments/{instrumentId}/limits`；★ 可用額度為**保守下界**並以 `holdAttribution` 揭露歷史佔用不可歸屬。前版 **1.15**（`CR-045` D4：§7.14 閉環改採**真 socket**（#A-242：嵌入式 Tomcat ＋ 真 HTTP，位址用 hostname）；★ 更正 #A-241 之「沙箱代理」錯誤解釋，真因為本機 Tomcat 僅綁 IPv6、IPv4 迴路掛住。前版 **1.14**（`CR-045` D4：§7.14 驗收 **②／④／⑤ 已閉合（#A-241）**——`product-service` 新增 `ParameterClosedLoopIntegrationTest`（真 Oracle ＋ 真 web 層）證明 PUT 落版／businessDate 版控／直改即反映（非快取）／同交易 Outbox／4 項負樣本；★ 更正前版「待寫入路徑俱備」之過度保守註記。前版 **1.13**（`CR-045` D4：§7.14 商品參數消費閉環首案**已實作並真機驗證閉環（#A-239）**。前版 **1.12**（`CR-045` D4：新增 §7.14 商品參數消費閉環首案）。前版 **1.11**（`CR-036`：**新增 §7.13 8-6 迄日之精確化**——改以**帳單出帳事件**（`statement.statement.issued.v1`）之**權威 `paymentDueDate`** 校正推算值；★ **契約無需變更**，僅新增一組訂閱。**真機驗證 3 項**含**負樣本**）。前版 **1.10**（`CR-035 §2.4`：**新增 §7.12 臨時額度串動**——POC-8 之 **8-6／8-7**） |
| 狀態 | 已核定（回溯補建） |
| 服務埠號 | 8083 |
| 套件根 | `com.beam.cardcore.lim` |
| 權威 schema | `CC_LIM` |
| 對應需求 | `ISS-2.4`（專用額度）、`SYS-2.1`（CQRS） |

---

## 1. 對應需求

| FR 編號 | 需求名稱 |
|---|---|
| `ISS-2.4` | 專用額度（永久／臨時／彈性） |
| `SYS-2.1` | 讀寫分離（CQRS） |

---

## 2. 服務職責與邊界

**承作**：
- 額度主檔（`account_limits`）維護
- 佔用／釋放（`limit_holds`）
- 額度調整申請（`limit_adjustments`）與狀態機（§8）
- **專用額度（`special_limits`）之現行定義維護**（POC-8；§7.9）

**不做**：
- 授權決策 → `authorization-service`（**含專用額度之交易時扣抵**，§10 #12）
- 帳務入帳 → `posting-service`

---

## 3. 領域模型

### 3.1 聚合

| 型別 | 職責 |
|---|---|
| `AccountLimit` | 額度主檔聚合（永久／臨時／彈性 + 已用）。不可變 |
| `LimitAdjustment` | 調整申請聚合 |
| `LimitAdjustmentStatus` | 調整申請狀態機（14 條轉換） |
| `SpecialLimit` | **專用額度之現行定義**（用途 × 金額 × 效期 × 狀態；§7.9）。不可變 |

### 3.2 狀態機：額度調整申請（SDD-005 §8）

| 來源 | 事件 | 目標 |
|---|---|---|
| — | 建立 | `DRAFT` |
| `DRAFT` | 送出（增幅 > 門檻） | `PENDING_APPROVAL` |
| `DRAFT` | 送出（增幅 ≤ 門檻） | `EFFECTIVE` |
| `PENDING_APPROVAL` | 覆核通過 | `EFFECTIVE` |
| `PENDING_APPROVAL` | 覆核駁回 | `REJECTED` |
| `EFFECTIVE` | 到期 | `EXPIRED` |
| `EFFECTIVE` | 撤銷 | `REVOKED` |

終態：`REJECTED`／`EXPIRED`／`REVOKED`。非法 → `CC-LIM-3001`；終態再轉換 → `CC-LIM-3002`。

### 3.3 狀態機：佔用（SDD-005 §3）

`HELD → RELEASED`／`EXPIRED`；釋放與部分釋放。

---

## 4. 程式結構

| 套件／類別 | 職責 |
|---|---|
| `domain/model/AccountLimit` | 額度主檔聚合 |
| `domain/model/LimitAdjustment` | 調整申請聚合 |
| `domain/model/LimitAdjustmentStatus` | 調整狀態機（serviceCode `LIM`） |
| `domain/model/SpecialLimit` | **專用額度現行定義**（§7.9；含 `scope_ref` 之 MCC 涵蓋判定，§7.10） |
| `domain/port/LimitRepository` | 主檔與佔用埠（`createHold` 含**池歸屬**，§7.10） |
| `domain/port/LimitAdjustmentRepository` | 調整申請埠 |
| `domain/port/SpecialLimitRepository` | **專用額度埠**（§7.9；含 `findApplicable`／`increaseUsed`，§7.10） |
| `domain/port/SpecialLimitPolicyRepository` | **政策埠**（§7.11；角色 × 客層 → 上限） |
| `domain/port/CallerIdentityPort` | ★ **呼叫者身分**（§7.11.2；**角色自權杖**，不接受請求帶入） |
| `domain/port/SpecialLimitTempAdjustmentRepository` | **串動紀錄埠**（§7.12；8-6／8-7） |
| `domain/port/TempAdjustmentRecoveryPort` | ★ **窄埠**（§7.12.3）——使繳款消費端不依賴串動服務之完整介面 |
| `application/SpecialLimitTempAdjustmentService` | **8-6 調高／8-7 FIFO 回收**（§7.12） |
| `infrastructure/messaging/PostingTransactionPostedListener` | 入帳事件消費者（**8-6 之觸發**；§7.12） |
| `infrastructure/messaging/StatementIssuedListener` | ★ 帳單出帳事件消費者（**8-6 迄日之校正**；§7.13、`CR-036`） |
| `application/LimitApplicationService` | 佔用／釋放／查詢（含 **SPECIAL 池路徑**與**適用性查詢**，§7.10） |
| `application/LimitAdjustmentService` | 申請／覆核／撤銷／到期／專用額度 |
| `infrastructure/jdbc/JdbcLimitRepository` | JDBC 實作 |
| `infrastructure/jdbc/JdbcLimitAdjustmentRepository` | JDBC 實作 |
| `infrastructure/jdbc/JdbcSpecialLimitRepository` | JDBC 實作（§7.9） |
| `infrastructure/scheduler/TemporaryLimitExpiryScheduler` | 臨時額度**與專用額度**到期排程 |
| `interfaces/rest/LimitController` | 佔用／釋放／歸戶額度 |
| `interfaces/rest/LimitAdjustmentController` | 申請／覆核／撤銷 |

---

## 5. 資料模型

| 表 | 關鍵欄位 | 實測約束 |
|---|---|---|
| `account_limits` | `account_id`、`currency`、`permanent_amount`、`temporary_amount`、`temp_start_date`、`temp_end_date`、`shadow_amount`、`used_amount`、`status`、`version` | UNIQUE(`account_id`,`currency`) |
| `limit_holds` | `id`、`account_id`、`authorization_id`、`hold_amount`、`released_amount`、`hold_type`、`status`、**`special_limit_id`** | `status IN ('HELD','RELEASED','EXPIRED')`；`hold_type IN ('GENERAL','SPECIAL','INSTALLMENT')`；★ `special_limit_id` 非 NULL ＝ 屬**專用額度池**（`CR-035 §2.2`） |
| `limit_adjustments` | `id`、`account_id`、`adjust_type`、`before_amount`、`after_amount`、`status`、`temp_start_date`、`temp_end_date`、`applicant_id`、`approver_id`、`effective_at`、`version` | `adjust_type IN ('PERMANENT','TEMPORARY','SPECIAL','SHADOW')`；`status IN (...)` |
| **`special_limits`** | `id`、`account_id`、**`currency`**、`purpose`、**`scope_ref`**、`amount`、`used_amount`、`effective_from`、`effective_to`、`status`、`version` | ★ 見 §7.9：`purpose IN ('TAX','INSURANCE','TUITION','MEDICAL','OTHER')`、`amount >= 0`、`status IN ('ACTIVE','EXPIRED','CANCELLED')`；`UNIQUE INDEX uk_special_limits_active` |
| **`special_limit_policies`** | `id`、`role_code`、`segment_code`、`currency`、`max_amount`、`status`、`version` | ★ 見 §7.11：`status IN ('ACTIVE','INACTIVE')`、`max_amount >= 0`；`UNIQUE (role_code, segment_code, currency)`（`V8`） |
| **`special_limit_temp_adjustments`** | `id`、`account_id`、`currency`、`special_limit_id`、`authorization_id`、`posting_id`、`raised_amount`、`recovered_amount`、`raised_on`、`raise_until`、`due_date_source`、`status`、`version` | ★ 見 §7.12：`due_date_source IN ('DERIVED','STATEMENT')`、`status IN ('OPEN','CLOSED')`；`UNIQUE INDEX uk_special_temp_posting`（`V9`） |

---

## 6. API 設計

| 端點 | 說明 | 錯誤碼 |
|---|---|---|
| `GET /api/v1/accounts/{accountId}/limits` | 歸戶額度 | `CC-LIM-1001` |
| `POST /api/v1/limit-holds` | 建立佔用 | `CC-LIM-1001` |
| `POST /api/v1/limit-holds/{holdId}/releases` | 釋放 | `CC-LIM-1001` |
| `POST /api/v1/limit-adjustments` | 提出申請 | `CC-LIM-1001` |
| `POST /api/v1/limit-adjustments/{id}/approval` | 覆核（APPROVE／REJECT） | `CC-LIM-3001/3002` |
| `POST /api/v1/limit-adjustments/{id}/revocation` | 撤銷 | `CC-LIM-3001` |
| `GET /api/v1/accounts/{accountId}/special-limits` | **歸戶專用額度（現行定義，§7.9）** | — |
| `GET /api/v1/accounts/{accountId}/special-limits/applicable` | **交易適用之專用額度（池選擇，§7.10）** | — |
| `GET /api/v1/special-limit-policies` | **列出政策（§7.11）** | — |
| `PUT /api/v1/special-limit-policies/{roleCode}/{segmentCode}` | **新增／更新政策（冪等，§7.11）** | `CC-LIM-1002` |

---

## 7. 關鍵邏輯

### 7.1 可用額度

```
可用 = 永久 + 有效之臨時 + 彈性 − 已用 − 未釋放之佔用
```

- **臨時額度有效性**：`temp_start_date ≤ today ≤ temp_end_date`（逾期不計入） |
- 可用為負時歸零。

> ⚠️ **專用額度（`special_limits`）不計入本式**——`BRD-004 POC-8` 之 `8-3` 明載
> 「使用專用額度**不佔卡人額度**」。專用額度為**獨立額度池**，由授權端於交易時**併查**（`§10 #12`，尚未實作）。

### 7.2 覆核門檻

- 增幅（`after − before`）超過 `cardcore.limit.approval-threshold-minor`（預設 500,000 分 = 5,000 元）→ 送覆核 |
- 未超過 → 直接 `EFFECTIVE` 並套用至主檔。

> ⚠️ **簡化模擬**：正式應由角色／權限系統決定申請者可核准額度。已於程式與文件標註，避免誤認已完成權限整合。

### 7.3 撤銷

`EFFECTIVE → REVOKED` **必須回復額度至 `before_amount`**——只改狀態不動主檔會留下「狀態已撤銷、額度卻是調整後數值」之兩套真相。

### 7.4 到期排程

`TemporaryLimitExpiryScheduler` 每 60 秒掃描「EFFECTIVE + TEMPORARY + temp_end_date < 今日」→ 轉 `EXPIRED` 且 `temporary_amount` 歸零（同一交易）。

### 7.5 額度事件輸出（`limit.limit.adjusted.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 推廣・事件 #2）

> **緣由**：`SAD-004 §5.2` 載明本事件（生產者 `limit`、主要消費者 `authorization`、用途「額度同步」），
> 惟**從未有人發射**（同 `#A-117`／`#A-118` 之卡片狀態事件，屬「**文件已載、程式全無**」型缺口）。
> 本節為該**生產端**之規格；消費端見 `SCS-001 §7.13`。

**主題**：`cc.limit.limit.adjusted`（命名規則 `SAD-004 §5.4`：`cc.<aggregateType>.<eventType 去版本>`）。

| 項目 | 值 |
|---|---|
| `eventType` | `limit.limit.adjusted.v1` |
| Outbox `aggregate_type` | `limit` |
| Outbox `event_type` | `limit.adjusted`（**不含**版本） |
| Kafka 分區鍵 | `accountId`（**同帳戶之事件落同一分區 → 聚合內有序**） |

**事件 payload（`v1`）**：

| 欄位 | 來源（limit-service `account_limits`） | 說明 |
|---|---|---|
| `accountId` | `account_id` | 歸戶識別碼（**亦為信封 `aggregateId`**） |
| `currency` | `currency` | 幣別（ISO 4217，3 碼） |
| `totalAmountMinor` | **套用後之總額度** ＝ 永久 ＋ 有效之臨時 ＋ 彈性 | 金額以**最小單位（分）**表示 |

> ⚠️ **`totalAmountMinor` 不是「本次調整之目標額度」**：`adjustment.afterAmount()` 僅為**該調整類型**之目標值
> （例：`TEMPORARY` 調整時它只是臨時額度）——若直接當總額度，會**低估**可用額度。
> 故 `applyToMaster` 於套用成功後**重讀主檔**，以 `AccountLimit.totalLimit(today)` 為 payload
> （對應讀模型 `account_limit_caches.total_amount` 之語意，見 `JdbcLimitPort`）。

**處理**：

1. **同交易寫 Outbox**：`applyToMaster` 套用主檔成功後，於**同一交易**（`@Transactional`）寫入 `outbox`（`SDD-001 §3.15` 形狀）；否則會出現「**額度已改、事件未發**」之落差。
2. **relay 發布**：`OutboxRelayScheduler` 週期性呼叫 `common-event` 之 `OutboxPublisher` 撈取 `published_at IS NULL` 者送 Kafka（`SDD-004 §4.3`）。
3. **信封欄位固定**（`SAD-004 §5.1`）；業務差異全在 `payload`。

**設計要點**：

- ★ **只在「已生效」路徑發射**：`applyToMaster` 僅由 `EFFECTIVE`（直接生效／覆核通過）呼叫；`PENDING_APPROVAL`／`REJECTED` **不發射**（額度未變）。
- ⚠️ **序列化為 `StringSerializer`**：Outbox `payload` 已是**信封 JSON 字串**，若經 `JsonSerializer` 會多包一層引號並轉義 → 消費端解析失敗（`SAD-004 §5.4`）。
- ⚠️ **`sequenceNo` 為「同 JVM 內嚴格遞增」之替代序號**（`LimitEventFactory.nextSequenceNo()`：以毫秒時間戳為基底、`max(now, last+1)`，故同毫秒多次調整**不碰撞**）；**多實例**部署時仍可能重複，**正式環境應改為帳戶層級之持久化序號**（`SAD-004 §5.4` 要求「同一聚合內依 `sequenceNo` 遞增」）。此為**已知簡化**，如實揭露（見 §10）。
- ⚠️ **撤銷（`§7.3`）與到期（`§7.4`）路徑尚未發射事件**（額度亦隨之變動）→ 見 §10 已知限制。

### 7.6 佔用事件輸出（`limit.hold.created.v1`／`limit.hold.released.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 推廣・事件 #4／#5）

> **緣由**：`SAD-002 §3.2` 明列本服務**發布** `limit.hold.created.v1`／`limit.hold.released.v1`，
> `SDD-006 §4.2` 亦已定義 payload，惟**從未有人發射**（同 `§7.5` 之型）。
> **權威歸屬**：`limit_holds` 之權威在本服務（`CC_LIM`）——故**由本服務發布佔用事件為無爭議**。
> ✅ **已化解（2026-10-01，`CR-028`）**：`SAD-002 §3.1` 原另稱 `authorization-service` 亦發布
> `authorization.hold.*`——經查證（**全庫程式碼零引用**、payload 逐欄相同、權威在 `CC_LIM`）
> 確認為「**同一事實兩個生產者**」→ **已廢除 `authorization.hold.*`**，hold 事件一律由本服務發布。

**主題**：`cc.limit.limit.hold.created`／`cc.limit.limit.hold.released`（命名規則 `SAD-004 §5.4`）。

| 項目 | `…hold.created.v1` | `…hold.released.v1` |
|---|---|---|
| `eventType` | `limit.hold.created.v1` | `limit.hold.released.v1` |
| Outbox `aggregate_type` | `limit` | `limit` |
| Outbox `event_type` | `limit.hold.created` | `limit.hold.released` |
| Kafka 分區鍵 | `holdId`（**同一佔用之事件落同一分區**） | `holdId` |

**payload（`v1`；欄位名依 `SDD-006 §4.2`）**：

| 欄位 | created | released | 來源 | 說明 |
|---|---|---|---|---|
| `holdId` | ✅ | ✅ | `limit_holds.id` | 佔用識別碼（**亦為信封 `aggregateId`**） |
| `accountId` | ✅ | ✅ | `limit_holds.account_id` | 歸戶識別碼 |
| `holdAmount` | ✅ | — | `limit_holds.hold_amount` | **最小單位（分）** |
| `holdType` | ✅ | — | `limit_holds.hold_type` | 佔用類型（如 `GENERAL`） |
| `authorizationId` | ✅ | — | `limit_holds.authorization_id` | **可選欄位**（2026-10-01 補，`CR-028`）——供 `authorization-service` 之本地投影（`CC_AUTH.LIMIT_HOLDS` 有同名列）**對應回授權**。⚠️ 依 `SAD-004 §5.3`「新增**可選**欄位**不升版**」；無對應授權時為 `null` |
| `currency` | ✅ | — | 請求參數 `currencyCode` | **可選欄位**（2026-10-01 補，批次 B）——`CC_AUTH.LIMIT_HOLDS.currency` 為 `NOT NULL`，消費端需要此值方能寫入本地投影；**原 payload 未帶**（**契約缺口**，見 `SCS-001 §7.15`）。同樣依 `SAD-004 §5.3` **不升版** |
| `instrumentId` | ✅ | ✅ | `limit_holds.instrument_id`（`created` 取入參；`released` 以 `findHoldInstrumentId` 回查） | ★ **可選欄位**（`CR-054` P1，2026-10-17）——交易載體識別，供投影把佔用歸到正確的卡。★ **補此欄之根因**：權威表早於 `V10` 即有值，但**事件原本不帶** → 投影永遠拿不到（「**權威表有、事件沒有**」）。`V10` 前之歷史佔用為 `null`（**不得回填、不得以 `cardId` 猜**）。依 `SAD-004 §5.3` **不升版** |
| `releasedAmount` | — | ✅ | 本次釋放金額 | **最小單位（分）**；**為本次釋放額，非累計** |

**處理**：

1. **同交易寫 Outbox**：`LimitApplicationService.hold()`／`release()` 於**同一交易**（`@Transactional`）寫入 `outbox`；否則會出現「佔用已成立、事件未發」之落差。
2. **`released` 之 `accountId` 由 DB 取得**：`release(holdId, amount)` 之入參**不含帳戶**——故於釋放成功後以 `findHoldAccountId(holdId)` 取得（**同一交易**）。
3. **relay 發布**：`OutboxRelayScheduler`（同 `§7.5`）。

**設計要點**：

- ⚠️ **`holdAmount` 為 `null` 之處理**：`createHold` 必然有金額，故 created 之 `holdAmount` **恆非 null**；**不設 `null` 分支**（避免死碼）。
- ✅ **`authorizationId` 已納入為「可選」欄位**（2026-10-01，`CR-028` 化解）：
  原載「刻意不納入（現行契約未定義）」——經查證 `CC_LIM.LIMIT_HOLDS` **本就有 `AUTHORIZATION_ID` 欄位**，
  且 `CC_AUTH.LIMIT_HOLDS`（本地投影）亦有同名列 → 消費端**需要它**才能對應回授權。
  依 `SAD-004 §5.3`「新增**可選**欄位**不升版**」→ **不升 `v2`**。
  ⚠️ **實作（補入該欄）屬批次 B**（`CR-028 §6`），**本輪未動程式**。
- ⚠️ **部分釋放會發射多次**：每次成功釋放各發一則（`releasedAmount` 為**該次**金額）；消費者若需累計須自行加總（**冪等仍由 `eventId` 保證**）。
- ⚠️ **序列化為 `StringSerializer`**（同 `§7.5`）。

**真機驗證（2026-10-01；真實 Oracle ＋ Kafka）**：

| 驗證 | 結果 |
|---|---|
| 生產（created） | ✅ `POST /api/v1/limit-holds`（30,000 分、`GENERAL`）→ `outbox` 落列（`limit.hold.created`、`aggregate_id`＝`holdId`）→ relay **標記已發布** |
| 生產（released） | ✅ `POST /api/v1/limit-holds/{holdId}/releases`（10,000 分、**部分釋放**）→ `outbox` 落列（`limit.hold.released`）→ relay **標記已發布** |
| Kafka | ✅ 主題 `cc.limit.limit.hold.created`／`cc.limit.limit.hold.released` **皆自動建立**；消費得信封，`aggregateId`＝`holdId`（**分區鍵正確**）、`sequenceNo` 遞增 |
| payload 契約 | ✅ created＝`{holdId, accountId, holdAmount:30000, holdType:"GENERAL"}`、released＝`{holdId, accountId, releasedAmount:10000}`——**與 `SDD-006 §4.2` 逐欄一致**；⚠️ `releasedAmount` 為**本次**釋放額（非累計） |
| 單元測試 | ✅ `LimitEventFactoryTest` 4 例（含 **payload 欄位數恰為契約值**、**不含 `authorizationId`** 之守門） |

---

### 7.7 繳款入帳事件消費（`payment.payment.posted.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 A-1）

> **緣由**：`SAD-002 §3.2` 明列本服務**訂閱** `payment.payment.posted.v1`（**恢復額度**）；
> `SCS-008 §8` 亦載「`limit-service`（額度回復）**經 Transactional Outbox 事件非同步通知**」。
> 惟**消費端從未實作**——`used_amount` 因此**永遠不會因繳款而下降**（`SCS-002 §10 #3` 之「已知限制」）。
> 本節即補上；**同時解除 §10 #3 之部分限制**。

**訂閱**：主題 **`cc.payment.payment.posted`**；群組 `limit-service`。

**處理**：

1. **冪等**：`eventId` 寫入 `consumed_events`（**與 `used_amount` 更新同交易**）；已存在 → 略過。
2. **亂序防護**：`sequenceNo` 落後於該 `aggregateId` 已消費之最大值 → 略過並 WARN。
   ⚠️ **本服務之亂序判準取自 `consumed_events` 之 `MAX(sequence_no) WHERE aggregate_id = ?`**
   （**不新增欄位至權威主檔 `account_limits`**——理由見下）。
3. **恢復額度**：`used_amount = GREATEST(0, used_amount − amount)`，`version + 1`（**僅此一欄**）。
4. **未知 `eventType`**：略過並 WARN（**不拋錯**，`SAD-004 §5.4`）。

**★ 設計要點（每一項皆為「不臆測」之選擇）**：

| 決策 | 理由 |
|---|---|
| **恢復金額 ＝ `amount`（繳款總額），不依 `allocations[]` 細分** | `used_amount` 之語意為「**已用額度總額**」（`§7.1`）。繳款使**未清償餘額**下降 `amount`——子帳（稅／費／息／本金）之細分屬**帳務**（`posting`）之職責，額度讀模型**不需也不應**複製該細分 |
| **`GREATEST(0, …)` 下限 0** | 溢繳（繳款 > 欠款）時 `used_amount` **不得為負**；超出部分屬**帳務之貸方餘額**，非額度概念 |
| **不使用 payload 之 `availableLimitAfter`** | 該欄位恆為 `null`（`SCS-008 §10 #5` 之既有設計）；且**額度之權威在本服務**——採用外部系統算出之額度值＝**權威外移**。本服務**自行**由 `used_amount` 推算，權威不出門 |
| **查無 `account_limits` 主檔 → 記 WARN、略過、且「不寫 `consumed_events`」** | `account_limits` 為**權威主檔**，**不得**因一則繳款事件而**憑空建列**。不寫 `consumed_events` 係為維持「**已記＝已套用**」之不變式（否則重放時會漏套用） |
| **亂序判準置於 `consumed_events` 而非 `account_limits`** | `account_limits` 為**權威主檔**，不應為事件消費而增設技術欄位（與 `authorization-service` 之 `card_status_caches`——**純讀模型**——情況不同） |
| **不快取** | — |

**真機驗證（2026-10-01；真實 Oracle ＋ Kafka；生產端與消費端同一輪）**：

| 驗證 | 結果 |
|---|---|
| 遷移 | ✅ `CC_LIM.consumed_events`（`V5`）建立成功 |
| 生產端（payment-service） | ✅ `POST /api/v1/payments`（200,000 分）→ `status=POSTED`、`LIMIT_RESTORED=1` → `outbox` 落列 → relay 標記已發布 |
| Kafka | ✅ 主題 `cc.payment.payment.posted` 自動建立；消費得信封，`aggregateId`＝`paymentId` |
| payload 契約 | ✅ `paymentId`／`accountId`／`amount`＝200000／`allocations[]`／`availableLimitAfter`＝**`null`**（**如實**，非臆造） |
| **消費端（★ 權威變更）** | ✅ `account_limits.used_amount` **500,000 → 300,000**（`version + 1`）；`consumed_events` 落列 |
| **冪等** | ✅ 重送**同 `eventId`** → `used_amount` **仍 300,000**、`consumed_events` 總數不變 |
| **亂序防護** | ✅ 送 `sequenceNo=1`（落後）→ `used_amount` **仍 300,000**（**未被重複扣減**） |
| 溢繳下限 | ✅ 送 `amount` 大於 `used_amount` → `used_amount` **收斂至 0**（**不為負**） |
| 單元測試 | ✅ `PaymentPostedProjectionServiceTest` 4 例 |

> ⚠️ **未竟（誠實揭露）**：**(i)** **`UNMATCHED`（重新佔額）與 `REVERSED` 之事件未實作**——
> 故「繳款後又轉未匹配」時，額度**不會被重新佔用**（`SCS-008 §8.2` 之 `LIMIT_RESTORED=0` 語意**未傳遞到本服務**）；
> **(ii)** 亂序判準為 per-aggregate（`paymentId`）——每筆繳款僅發一次事件，實務上鮮少觸發，仍依 `SAD-004 §5.4` 實作以求一致。

---

### 7.8 繳款「重新佔用」事件消費（`payment.payment.unmatched.v1`／`.reversed.v1`）——2026-10-01 新增（`CR-030 §2 #9`）

> **緣由（★ 真缺陷之修補）**：`SCS-008 §7`／`§8.2` 明載繳款轉 `UNMATCHED`（逾三日未收銷帳檔）與**沖正**
> 皆會**撤銷額度恢復**（`LIMIT_RESTORED = false`），惟原僅發射 `posted.v1` →
> **額度不會被重新佔用** → **可用額度永久高估**（**超額放行風險**）。本節即補上該消費端。

**訂閱主題**：`cc.payment.payment.unmatched` 與 `cc.payment.payment.reversed`（`SAD-004 §5.4` 命名規則）。

| 項目 | 值 |
|---|---|
| 事件型別 | `payment.payment.unmatched.v1`／`payment.payment.reversed.v1` |
| 分區鍵 | `paymentId`（信封 `aggregateId`） |
| **消費效果** | **重新佔用額度**：`account_limits.used_amount += amount`（`restoreUsedAmount` 之**反向**） |

**⚠️ 為何本消費「不是」重做同步已做之寫入**（`CR-030 §2 #16` 之界限）：
「撤銷額度恢復」**目前無任何路徑涵蓋**（`markUnmatched` 為批次、`reverse` 為 API，**皆未通知 `limit`**）。
`#16` 禁止的是「**重複做同步已做的事**」；**不禁止「做沒人做的事」**。

**與 `§7.7`（恢復）之關鍵差異**：本事件之 payload **帶 `currency`**（`CR-030 §2 #10` 通則）
→ 可**精確定位**額度主檔（`WHERE account_id = ? AND currency = ?`），
**無須** `§7.7` 之「該帳戶恰 1 列才更新」保守規則；未帶 `currency` 時仍走保守 fallback（容忍舊事件）。

**處理**（複用 `§7.7` 之三步，皆於**同一交易**）：
① 冪等（`consumed_events.event_id`）→ ② 亂序（`sequence_no <= MAX(aggregate_id)`）→
③ `reoccupyUsedAmount` ＋ 寫 `consumed_events`。
⚠️ **查無主檔時「不寫 `consumed_events`」**——維持「**已記＝已套用**」之不變式。

### 7.9 專用額度（POC-8）之寫入與查詢——2026-10-02 新增（`CR-014 v1.1`）

> **緣由**：`applyAdjustedAmount` 之 `switch` **無 `SPECIAL` 分支**（原註解「SPECIAL 屬另一張表，
> 本階段不處理」）→ `POST /limit-adjustments`（`adjustType=SPECIAL`）**必然失敗**；
> 且查詢端點（`C-11`）當時**讀 `LIMIT_ADJUSTMENTS`**（調整視角），**非**專用額度之現行定義。
>
> **★ 前置查證（`CR-014 v1.1 §0`）**：`CC_LIM.SPECIAL_LIMITS` **早已存在**——
> 由 `V1__baseline.sql` 建立，該檔係 `tools/gen_flyway_baseline.py` 依 **`SDD-001 §3.4`** 自動產生。
> `CR-014 v1.0` 曾誤稱之為「新表」（根因：「實測」只掃 Java、未掃 `db/migration` 之 DDL）。
> 故本節之工作為「**接線既有表**」，**非建表**。

#### 7.9.1 權威分工（`CR-030` P1：單一真實來源）

| 表 | 職責 | 生命週期 |
|---|---|---|
| `limit_adjustments` | 申請／覆核／撤銷之**稽核軌跡**（誰申請、誰覆核、何時、為何） | **不可變**異動紀錄（狀態機 `§3.2`） |
| `special_limits` | 專用額度之**現行定義**（用途、金額、效期、狀態） | **可變**現行狀態（8-1 之增／改／刪） |

→ 兩者**不是重複**：前者答「**這筆額度是怎麼來的**」，後者答「**現在這筆額度是多少**」。

#### 7.9.2 值域對映（`special_limits` 之 `CHECK` 為權威）

| 本服務概念 | `limit_adjustments.status`（軌跡） | `special_limits.status`（定義） |
|---|---|---|
| 待覆核 | `DRAFT`／`PENDING_APPROVAL` | **不入本表**（未生效者無現行定義） |
| 已生效 | `EFFECTIVE` | `ACTIVE` |
| 已撤銷 | `REVOKED` | `CANCELLED` |
| 已到期 | `EXPIRED` | `EXPIRED` |

> ⚠️ **`PENDING_APPROVAL` 刻意不寫入 `special_limits`**——本表之語意為「**現行有效之定義**」，
> 未生效之申請若寫入，將使 `status='ACTIVE'` **說謊**（授權端可能據以放行）。

`purpose` 值域（**不自由化**，沿用既有 `CHECK`）：
`TAX`／`INSURANCE`／`TUITION`／`MEDICAL`／`OTHER`。
不合法之 `purpose` → **`CC-LIM-1003`**（「專用額度不可用／未設定或已過期」——既有錯誤碼即為此設計）。

#### 7.9.3 寫入路徑

1. **`apply()` 之基準額度**：`SPECIAL` 之 `before` **取自「現行有效之同（歸戶, 幣別, 用途）專用額度」**（無則 0），
   **不是**主檔之 `permanent_amount`。
   ⚠️ 否則覆核門檻（`§7.2`，以「增幅」判定）會以**錯誤基準**計算 → 該送覆核者可能直接生效。
2. **`applyToMaster()`**：`SPECIAL` → `SpecialLimitRepository.upsertActive(...)`（**同一交易**），
   **不走** `applyAdjustedAmount`。
   - `effectiveFrom` ＝ 請求之 `tempStartDate`，**未帶則取今日**（本表該欄 `NOT NULL`）。
   - 主檔（`account_limits`）之存在**仍為前提**（`§7.2` 之既有檢核不變）——
     `8-4`「未設定則用卡人額度」之前提是**卡人額度存在**。
3. **`revoke()`**：`SPECIAL` → `cancelActive(...)`（`ACTIVE` → `CANCELLED`）；
   ⚠️ **不得**走 `applyAdjustedAmount`（該表無 SPECIAL 分支 → 會誤報「回復額度失敗」）。
4. **`upsertActive` 之語意**：先 `UPDATE ... WHERE account_id=? AND currency=? AND purpose=? AND status='ACTIVE'`；
   影響 0 列 → `INSERT`。歷史列（`CANCELLED`／`EXPIRED`）**保留**，不覆寫。

#### 7.9.4 唯一性不變式（★ 防「重複計算專用額度」）

```
CREATE UNIQUE INDEX uk_special_limits_active
    ON special_limits (account_id, currency, purpose,
                       CASE WHEN status = 'ACTIVE' THEN 'ACTIVE' END);
```

- 語意：**一歸戶 × 一幣別 × 一用途，至多一筆 `ACTIVE`**。
- Oracle B-tree 唯一索引**不索引全 `NULL` 鍵** → 非 `ACTIVE` 之歷史列可留多筆。
- **為何必要**：若同（歸戶, 幣別, 用途）存在兩筆 `ACTIVE`，授權端之專用額度檢核
  將**重複計算**（可用額度被高估）→ 屬**超額放行風險**。
- **併發**：兩請求同時對同一鍵 `upsert` 時，後者可能因唯一鍵衝突而**失敗（fail-closed）**，
  不會產生重複列——**寧可失敗，不可多算**。

#### 7.9.5 查詢端點（`GET /api/v1/accounts/{accountId}/special-limits`）

- **來源**：`special_limits`（**現行定義**），條件 `account_id = ?`、`status = 'ACTIVE'`，
  依 `purpose` 排序。**不再讀 `LIMIT_ADJUSTMENTS`**。
- **回應**：`SpecialLimitsView{ accountId, count, items[] }`；
  `SpecialLimitItem{ specialLimitId, accountId, currency, purpose, amountMinor, usedAmountMinor,
  status, effectiveFrom, effectiveTo }`。
- ★ **契約破壞性變更**（`CR-014 v1.1 §7`）：欄位由「調整視角」改為「定義視角」
  （移除 `adjustmentId`／`applicantId`、值域改 `ACTIVE／EXPIRED／CANCELLED`、
  `tempStartDate／tempEndDate` → `effectiveFrom／effectiveTo`、新增 `usedAmountMinor`）。
  經查證**消費端為零**（前端無引用）→ 直接修正 schema，**不另立 `v2`**。
- 無資料 → **空清單**（`count=0`），**非** `null`。

#### 7.9.6 到期排程（併入 `§7.4` 之排程）

`ACTIVE` 且 `effective_to < 今日` → `EXPIRED`（同一交易、批次處理）。

**為何必要**（非「錦上添花」）：若不轉態，
1. `status='ACTIVE'` **說謊**——查詢端點將回傳**已過期**之專用額度；
2. `uk_special_limits_active` 會**擋住**同（歸戶, 幣別, 用途）之新額度（新申請之 `INSERT` 撞唯一鍵）。

#### 7.9.7 真機驗證（2026-10-02；真實 Oracle 23.26 ＋ Keycloak M2M ＋ 服務 8083）

**前置**：`V6` 遷移經 Flyway 套用（真機複查：`special_limits` 新增 `CURRENCY CHAR NOT NULL`；
索引 `IDX_SPECIAL_LIMITS_ACCOUNT`（`account_id, status`）與
`UK_SPECIAL_LIMITS_ACTIVE`（`account_id, currency, purpose, SYS_NC00013$`←函式索引）皆已建立）。

| # | 驗證 | 結果 |
|---|---|---|
| ① | 基線查詢 | ✅ `GET /accounts/{id}/special-limits` → `count=0`（表原為 0 列） |
| ② | SPECIAL 申請（未超限） | ✅ `POST /limit-adjustments`（`TAX`／200,000 分）→ **201**、`status=EFFECTIVE`、`beforeAmount=0`（**基準取自專用額度池，非主檔 10,000,000**） |
| ③ | 查詢反映 | ✅ `count=1`、`purpose=TAX`、`amountMinor=200000`、`status=ACTIVE`、`effectiveFrom=2026-10-02` |
| ④ | **upsert**（同用途再次申請） | ✅ 再送 `TAX`／300,000 → `beforeAmount=200000`（**讀到既有專用額度**）→ `special_limits` **仍為 1 列**（`UPDATE`，非新增） |
| ⑤ | 負樣本：`purpose=CREDIT` | ✅ **422 `CC-LIM-1003`**（訊息含允許值），**未落庫** |
| ⑥ | 負樣本：`purpose` 未帶 | ✅ **422 `CC-LIM-1003`**（不得以 `null` 落庫觸發 `ORA-02290`） |
| ⑦ | 超門檻 → 待覆核 | ✅ `MEDICAL`／600,000（增幅 > 500,000）→ `PENDING_APPROVAL`；★ **`special_limits` 無 MEDICAL 列**（未生效者不寫入） |
| ⑧ | 覆核通過 | ✅ `POST …/{id}/approval`（APPROVE）→ `EFFECTIVE`；`special_limits` 出現 `MEDICAL`／600,000／`ACTIVE` |
| ⑨ | 撤銷 | ✅ `POST …/{id}/revocation` → `REVOKED`；`special_limits` 該列轉 `CANCELLED`；查詢清單僅剩 `MEDICAL` |
| ⑩ | ★ **權威來源（非快取）證明** | ✅ **繞過服務**直接 `UPDATE special_limits SET amount=777777` → `GET` 立即反映 **777777**（若為快取則仍為 600,000） |
| ⑪ | ★ **到期**（排程 ＋ 讀側日期條件） | ✅ 直接將 `effective_to` 設為昨日：**排程執行前**查詢即已 `count=0`（**日期條件把關**，非只靠 `status`）；約 **40 秒**後排程將其轉 `EXPIRED` |
| ⑫ | ★ **不佔卡人額度**（8-3） | ✅ 全程 `account_limits` 之 `permanent_amount`／`version` **完全未變** |
| ⑬ | 軌跡 vs 定義之分離 | ✅ `limit_adjustments` 保有 5 筆 SPECIAL 申請／覆核紀錄（含 `before_amount`／`after_amount`／`applicant_id`），與 `special_limits` 之**現行定義**各自為政、互不取代 |
| 單元測試 | | ✅ `LimitAdjustmentServiceSpecialTest` **15 例**（0 失敗）；`LayeringTest` 通過 |
| 掃描器 | | ✅ `scan_scaffold`／`scan_mockdata`／`scan_faketest` 各 **0 命中**；`validate_sql` **0 違規**；`verify_contract_dto`（limit-service）**0 不符** |

> ⚠️ **併同修正之契約缺陷（真機測試揭露）**：`POST /limit-adjustments` 之實作原以
> `targetAmount` 為 JSON 欄位名，而契約宣告 `amount` → **依契約送件者一律 400**
> （`CC-PLT-4002 請求主體格式錯誤`，實測）；且實作之 `applicantId` 為必填而契約**漏列**
> （送件後回 `CC-PLT-4003 請求參數驗證失敗：applicantId 不得空白`）。
> 已一併修正：實作欄位改名為 `amount`、契約補 `applicantId`（必填）與 `currency`。
> **根因**：`verify_contract_dto` 僅核對**回應**型別，**請求** DTO（巢狀 record）不在關卡內 → 靜默漂移。

### 7.10 專用額度池之佔用與適用性（POC-8 之 8-3／8-5）——2026-10-02 新增（`CR-035 §2.1／§2.2`）

> **緣由**：`CR-014 v1.1` 完成後，專用額度**可設定但不能動用**——授權端完全不查本表，
> 故 `BR-04-01`（不佔卡人額度）與 8-5（超額拒絕）**皆不成立**。本節補上「池」這一半。

#### 7.10.1 用途範圍（`scope_ref`；`CR-035 §2.1`）

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

**選擇順序**（多筆命中時；**保證決定性**）：
① `scope_ref` **非空且命中**者優先（較特定）→ ② `scope_ref` 為 `NULL` 者 → ③ 依 `purpose`、`id` 升冪。

> ⚠️ **交易 MCC 為空**且 `scope_ref` 非空 → **不命中**（無法證明，**不臆測**）。

#### 7.10.2 池之佔用與釋放

| 動作 | 效果 |
|---|---|
| `hold`（`specialLimitId` 非空） | `special_limits.used_amount += 金額`（**條件更新**：`used_amount + ? <= amount`）＋ `limit_holds` 寫入 `hold_type='SPECIAL'`、`special_limit_id` |
| `hold`（`specialLimitId` 為空） | **既有卡人路徑不變**（`hold_type='GENERAL'`、`special_limit_id` 為 `NULL`） |
| `release` | 若該佔用有 `special_limit_id` → **同交易** `used_amount -= 釋放額`（`GREATEST(0, …)`） |

★ **`used_amount` 之語意為「佔用中」**（非「已入帳未清償」）——專用池之請款比對訊號即為「釋放」。
（8-6／8-7 之臨時額度串動為**另一條路徑**，見 `CR-035 §2.4`／Phase 3。）

★ **為何是條件更新而非「先查後改」**：兩筆併發授權可能同時通過檢查 → **超額佔用專用池**。
以單一 `UPDATE … WHERE used_amount + ? <= amount` 原子完成「檢查＋佔用」，
影響 0 列即**如實失敗**（`CC-LIM-1003`）。

#### 7.10.3 適用性端點（`GET /accounts/{accountId}/special-limits/applicable`）

| 參數 | 說明 |
|---|---|
| `currency` | 幣別（預設 `TWD`） |
| `mcc` | 交易之 MCC（可選） |
| `amount` | 交易金額（分） |

**回應** `SpecialLimitApplicabilityView`：`applicable`／`specialLimitId`／`purpose`／`scopeRef`／
`availableAmountMinor`／`reason`。

> ★★ **`applicable=false` 回 200，非 4xx**——「無適用」與「池內不足」皆為**正常結果**。
> 如此授權端才能區分：
> - **`applicable=false`** → 退回卡人額度（8-4）或拒絕（8-5）；
> - **5xx／連線失敗** → **Stand-in 降級**（`SCS-001 §7.16`）。
>
> 若兩者同以 4xx 表達，授權端將**無法區分「客戶沒有專用額度」與「額度服務壞了」**。

#### 7.10.4 真機驗證（2026-10-02；真實 Oracle ＋ 真實授權鏈路 8081 → 8083）

**環境**：`V7` 遷移套用；歸戶 `…0002`（卡 `4222222222222222`，`account_limits.permanent=410,000`）；
TAX 專用額度 `amount=20,000`、`scope_ref='9311,9399'`；`cardcore.auth.manual-review` 門檻 20,000 分。

| # | 驗證 | 結果 |
|---|---|---|
| ① | `scopeRef` 可經 API 設定 | ✅ `POST /limit-adjustments`（`scopeRef="9311,9399"`）→ 201；DB `scope_ref` 落地 |
| ② | 適用性：MCC 命中 | ✅ `mcc=9311` → `applicable=true`、`availableAmountMinor=20000` |
| ③ | 適用性：MCC 不命中 | ✅ `mcc=5411` → `applicable=false`、`reason=查無適用…（無命中）` |
| ④ | ★★ **8-3：走專用池** | ✅ `POST /authorizations`（`9311`／15,000 分）→ **APPROVED**；DB：`limit_holds.hold_type='SPECIAL'` 且 `special_limit_id` 已設；`special_limits.used_amount=15000` |
| ⑤ | ★★ **不佔卡人額度（`BR-04-01`）** | ✅ ④ 之 `availableLimit` 為 **310,000**（＝410,000 − 既有 GENERAL 佔用 100,000；**SPECIAL 佔用不計入**）；`account_limits` 之 `permanent`／`used`／`version` **全程未變** |
| ⑥ | 8-4：MCC 不符 → 回歸卡人池 | ✅ 同卡同額、`mcc=5411` → APPROVED；`hold_type='GENERAL'`、`special_limit_id` 為 `NULL`；`availableLimit` 310,000 → **295,000**（確由卡人池扣減） |
| ⑦ | 專用池不足 → 適用性 false（**仍回 200**） | ✅ `reason=適用之專用額度不足：可用 5000、請求 15000`；`specialLimitId` 仍回報（供判讀） |
| ⑧ | 池不足時**回退**卡人池 | ✅ 同筆 → APPROVED、`availableLimit` 295,000 → 280,000（走卡人池） |
| ⑨ | ★ **8-5：兩池皆不足 → 拒絕** | ✅ 將 `permanent` 暫降為 5,000 → **DECLINED／respCode 51**、`holdId=null`；`checkResults` 之 `LIMIT=false`、其餘 true |
| ⑩ | 釋放 → 專用池**對稱回沖** | ✅ `POST /limit-holds/{id}/releases`（15,000）→ 200；`used_amount` **15000 → 0** |
| ⑪ | 併發防護（設計） | ✅ 以 `UPDATE … WHERE used_amount + ? <= amount` 原子檢查；影響 0 列即拒絕（單元測試覆蓋） |
| 單元測試 | | ✅ `limit-service` **41 測 0 失敗**（新增 `SpecialLimitPoolTest` **13**）；`authorization-service` **65 測 0 失敗**（新增 5） |
| 掃描器 | | ✅ `scan_scaffold`／`scan_mockdata`／`scan_faketest` 各 0 命中；`validate_sql` 0 違規；`verify_contract_dto`（limit-service）0 不符 |

> ⚠️ **驗證方式之揭露**：授權端（8081）之 ACL **不收任何服務**（`SAD-002 §5`：`authorization-service: []`），
> 故 e2e 期間**暫時**以 `CARDCORE_SECURITY_ENABLED=false` 啟動 `authorization-service` 與 `limit-service`
> （否則出站憑證為 NoOp → limit-service 回 401 → 全數 Stand-in）。
> **驗證後已還原為 SSO 開啟**。此為環境操作，非程式變更。

### 7.11 專用額度之「可設定上限」政策（POC-8 之 8-2）——2026-10-02 新增（`CR-035 §2.3`）

> **緣由**：`BRD-001 §5.4 BR-04-03`「額度異動之可設定上限依**角色 ＋ 客層**參數化決定」。
> 在此之前專用額度**無任何上限機制**（僅受 `purpose` 值域與覆核門檻約束）。

#### 7.11.1 政策表（`special_limit_policies`，`V8`）

| 欄位 | 說明 |
|---|---|
| `role_code` | 角色——★ **權威**（見 §7.11.2） |
| `segment_code` | 客層；`*` ＝ 該角色**不限客層** |
| `currency` | 幣別 |
| `max_amount` | **可設定上限**（分） |
| `status` | `ACTIVE`／`INACTIVE`（★ **停用**以此表達，**不做 DELETE**——政策屬稽核相關設定） |

`UNIQUE (role_code, segment_code, currency)`。

#### 7.11.2 ★★ 角色之來源：**自「已驗證之權杖」決定，不接受請求帶入**

| 序 | 條件 | `role_code` | `verified` |
|---|---|---|---|
| ① | **服務權杖**：`azp` 以 **`cc-`** 開頭（**與 `ServiceCallerAclManager` 同一判準**） | `azp`（如 `cc-payment-service`） | `true` |
| ② | **使用者權杖**：`realm_access.roles`（去 `ROLE_` 前綴、**排除預設／雜訊角色**） | **字典序最小**者 | `true` |
| ③ | 皆無（**認證停用**／匿名） | **`UNVERIFIED`** | `false`（併記 WARN） |

> ★★ **序 ① 必須先於序 ②**——**實測**：M2M 權杖之 `realm_access.roles` 為
> `["default-roles-cardcore", "offline_access", "uma_authorization"]`，**皆為無區辨力之預設角色**；
> 若先取 realm roles，角色會被解析為 **`offline_access`**（**荒謬**），政策永遠查不到。
> 此為**真機驗證時發現**（第一版實作即踩此坑），已修正並補單元測試釘住。

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

#### 7.11.3 客層之來源（★ **非權威**，如實揭露）

`segment_code` **由請求帶入**（可選，未帶 ＝ `*`）。**為何無法自動取得**（真機查證）：

| # | 障礙 | 證據 |
|---|---|---|
| 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` **定位為「政策查詢鍵」而非授權判斷**（真正的權限在**角色**，而角色已不可偽造）。
**後續**：若客戶要求自動取得，需三項變更（見 `CR-035 §5 #2`）。

#### 7.11.4 檢核與查詢

**檢核時點**：`apply()`，`adjustType='SPECIAL'` 時——`afterAmount ≤ 適用政策之 max_amount`。
**僅 SPECIAL 適用**（永久／臨時／彈性之權限上限屬 §7.2 既有範疇）。

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

> ⚠️ **不設 `('*','*')` 萬用列**：那會使「忘了設定政策」與「刻意全域放行」**無法區分**。

#### 7.11.5 政策維護端點

| 端點 | 說明 |
|---|---|
| `GET /api/v1/special-limit-policies` | 列出全部政策（維運／DEMO） |
| `PUT /api/v1/special-limit-policies/{roleCode}/{segmentCode}` | 新增或更新（**冪等**）；停用 ＝ `status=INACTIVE` |

> ⚠️ **端點本身之權限（誠實揭露）**：目前僅受本服務之**一般認證**保護——
> **任何已驗證之呼叫者皆可設定政策上限**。正式應限定特定角色，
> 惟平台**無 RBAC 資料源**可資判斷 → 列後續（`CR-035 §5 #3`）。

#### 7.11.6 真機驗證（2026-10-02；真實 Oracle ＋ Keycloak M2M）

| # | 驗證 | 結果 |
|---|---|---|
| ① | ★ **角色解析** | ✅ 角色為權杖之 **`azp`＝`cc-payment-service`**（**非** `offline_access`／`default-roles-cardcore`） |
| ② | 申請 ≤ 上限 | ✅ 200,000 ≤ 300,000 → **201**（`EFFECTIVE`） |
| ③ | ★ **申請 > 上限 → 拒絕** | ✅ 400,000 > 300,000 → **422 `CC-LIM-1002`**「超過可設定上限：申請 400000、上限 300000（角色 cc-payment-service／客層 *）」 |
| ④ | ★ **上限為活值**（非寫死） | ✅ `PUT` 上限調為 500,000 → **同一請求** → **201** |
| ⑤ | 角色間互不影響 | ✅ 另設 `cardcore-csr` 上限 100,000 → 本次呼叫**不受影響** |
| ⑥ | 查無政策 → 拒絕（fail-closed） | ✅ 見 `#A-155` 之首次驗證（未設政策時 422「查無適用之可設定上限政策」） |
| 單元測試 | | ✅ `limit-service` **55 測 0 失敗**（新增 `SecurityContextCallerIdentityTest` **9** ＋ 政策檢核 **5**） |

> ⚠️ **序 ③（認證停用 → `UNVERIFIED`）以單元測試覆蓋**（`SecurityContextCallerIdentityTest`），
> **未於真機重跑**——真機驗證該路徑需暫時關閉認證（連帶影響其他驗證）。

### 7.12 臨時額度串動（POC-8 之 8-6／8-7）——2026-10-02 新增（`CR-035 §2.4`）

> **緣由**：`BR-04-02`「專用額度請款後，須調高卡人臨時額度至**繳款截止日 + 7 日**；繳款後調降」。

#### 7.12.1 8-6：請款 → 調高（消費 `posting.transaction.posted.v1`）

★ **本服務原已列為該事件之消費者（`SAD-002 §3.2`），惟從未實作**。

**處理**（同一交易）：
① 依 payload 之 `authorizationId` 查 `limit_holds`（`hold_type='SPECIAL'`）——
**查無即不處理**（該筆未用專用額度，**正常略過**）；
② **釋放該佔用**（授權請款比對，`BR-03-02`）→ 專用池 `used_amount` **對稱回沖**；
③ **調高臨時額度**：`temporary_amount += 請款金額`（★ **相對增量**，**非** `applyAdjustedAmount` 之絕對值）；
④ 寫入**串動紀錄**（供 8-7 精確扣回）。

**契約缺口之補正**：`posted.v1` 原 payload **不含 `authorizationId`** → 補為**可選**欄位
（來源 `CC_POSTING.TRANSACTIONS.AUTHORIZATION_ID` **早已存在**，**不需臆造**；
依 `SAD-004 §5.3` **不升版**）。⚠️ **缺該欄之舊事件 → 正常略過**（不臆測）。

#### 7.12.2 ★★ 繳款截止日之來源（**如實揭露**）

本服務之 schema 為 `CC_LIM`，**不得**讀 `CC_STMT`（**跨 schema**，`SAD-003`／`SQL-07`）。
故迄日以「**入帳日 ＋ 帳單週期（預設 25）＋ 7 日**」**推算**，並於紀錄標記
`due_date_source = DERIVED`——**不臆造為「帳單之繳款截止日」**。

> ⚠️ **`CR-035 §2.4` 之原設計在此更正**：原載「以 `CC_STMT.STATEMENTS.payment_due_date`
> 之最近一期為準」——該讀取為**跨 schema**，**不可行**。
> 精確值需 `statement` 整合（或於 `posted.v1` 補 `paymentDueDate`），列後續。
> 可配置：`cardcore.limit.special-temp-raise.statement-cycle-days`／`.grace-days`。

#### 7.12.3 8-7：繳款 → 調降（併入既有之繳款事件消費端）

由 `PaymentPostedProjectionService`（`§7.7`）於**恢復額度之同一交易**內呼叫：
依串動紀錄**先進先出**回收 → `temporary_amount -= 回收額`（`GREATEST(0, …)`），
回收完畢轉 `CLOSED`。

> ★★ **為何必須逐筆紀錄、不得直接歸零**：`account_limits.temporary_amount` 可能**同時**承載
> 「**人工核准之臨時額度**」（`§7.2` 之申請流程）——直接歸零會**誤砍客戶經核准之額度**。
>
> ★ **多幣別 → 不臆測**：繳款事件之 payload **無 `currency`**（`§10 #10`）→
> 未回收紀錄橫跨多幣別時**略過並 WARN**（寧可不回收，不可錯扣）。

#### 7.12.4 真機驗證（2026-10-02；真實 Oracle ＋ **真實 Kafka**）

| # | 驗證 | 結果 |
|---|---|---|
| ⓪ | `V9` 遷移 | ✅ `CC_LIM.special_limit_temp_adjustments` 建立 |
| ① | 前置：建立專用額度佔用 | ✅ `hold_type='SPECIAL'`；`used_amount=15000`；`temporary_amount=0` |
| ② | ★ **8-6：發佈 `posted.v1`（帶 `authorizationId`）** | ✅ `temporary_amount` **0 → 15000**；`temp_start=2026-10-02`、**`temp_end=2026-11-03`**（＝入帳日＋25＋7）；串動紀錄 1 筆（`raised=15000`、`due_date_source=DERIVED`、`OPEN`） |
| ③ | ★ 請款比對釋放佔用 | ✅ 專用池 `used_amount` **15000 → 0** |
| ④ | ★ **8-7：繳款 10,000（部分回收）** | ✅ `temporary_amount` **15000 → 5000**；紀錄 `recovered=10000`、仍 `OPEN` |
| ⑤ | ★ **8-7：再繳 5,000（回收完畢）** | ✅ `temporary_amount` **→ 0**；紀錄 `recovered=15000`、**`CLOSED`** |
| 單元測試 | | ✅ `limit-service` **64 測 0 失敗**（新增 `SpecialLimitTempAdjustmentServiceTest` **9**）；`posting-service` **35 測 0 失敗**（新增 2） |

> ⚠️ **驗證方式之揭露**：②／④／⑤ 以**真實 Kafka 發佈事件**驗證**消費端**（真實 Kafka ＋ 真實 Oracle）；
> **生產端**（`posting-service` 之 payload 補 `authorizationId`）以**單元測試**驗證
> （`posted.v1` 恰 9 欄、`authorizationId` 存在且可為 `null`）——
> **未**以真實 `POST /postings` 端到端重跑（該路徑需 `posting` 重啟與 `pricing` 相依）。

### 7.13 8-6 迄日之精確化（`CR-036`）——2026-10-02 新增

> **緣由**：`§7.12.2` 之迄日為**推算值**（入帳日＋帳單週期＋7）。而真正的繳款截止日
> ＝「**該筆稅款所屬帳單**之帳單日 ＋ 25 天」，取決於「入帳日是否已過帳單日」
> → 推算式最長可**高估近一個帳單週期**。
> **為何不能直接算**：帳單日（`ACCOUNTS.STATEMENT_DAY`）在 `CC_PARTY`，
> 而本服務之 schema 為 `CC_LIM`——**跨 schema 讀取為架構級禁止**（`SAD-003`／`SQL-07`）。

#### 7.13.1 ★★ 關鍵洞察：精確值**在原理上**只有帳單出帳後才存在

在帳單出帳之前，「該筆稅款屬於哪一期帳單」**尚未確定**——任何在請款當下算出的值都是猜測。
→ **正解不是「算得更準」，而是「在精確值出現時校正」**。

#### 7.13.2 兩階段（調高 → 校正）

| 階段 | 觸發 | 動作 | `due_date_source` |
|---|---|---|---|
| ① 調高 | `posting.transaction.posted.v1` | `temporary_amount += 金額`；迄日先給推算值 | `DERIVED` |
| ② **校正** | **`statement.statement.issued.v1`** | 以帳單之**權威 `paymentDueDate`** 覆寫 `raise_until = paymentDueDate + 7`；同步 `temp_end_date` | **`STATEMENT`** |

**★ 校正之歸屬條件**（**避免套錯期別**）：只有「**在該筆調高之後才出帳**」的帳單才承載該筆稅款
→ 僅校正 `status='OPEN'` **且** `due_date_source='DERIVED'` **且** `raised_on ≤ 出帳日` 之紀錄。
校正後標記 `STATEMENT` → **不重複校正**（冪等）。

**「出帳日」之來源**：事件信封之 **`occurredAt`**（該事件即「出帳」之事實）——
**不**由 `paymentDueDate − 25` 反推（那會把 25 天慣例**硬編**進程式）。

**`temp_end_date` 之同步**：＝ 該歸戶**所有 OPEN 紀錄之最晚迄日**——
★ **取最晚**：不得因校正某一筆而**縮短**另一筆之窗口。

#### 7.13.3 契約與架構：**契約無需變更**，僅新增一組訂閱

`statement.statement.issued.v1` 之 payload **早已包含** `accountId`／`paymentDueDate`
（`SCS-011 §7.2`）→ **不升版、不增欄**。
架構變更：`SAD-004 §5.2` 之消費者 `notification` → **`notification` ＋ `limit`**；
`SAD-002 §3.2` 之訂閱清單同步。**不新增同步呼叫、不開放任何 ACL**。

#### 7.13.4 真機驗證（2026-10-02；真實 Kafka ＋ 真實 Oracle）

| # | 驗證 | 結果 |
|---|---|---|
| ① | 8-6 調高（推算） | ✅ `temp_end_date=2026-11-03`、紀錄 `raise_until=2026-11-03`、`due_date_source=DERIVED` |
| ② | ★ **負樣本**：出帳日 **2026-09-30**（**早於**調高日 10-02） | ✅ **不校正**（仍 `2026-11-03`／`DERIVED`）——該期帳單不承載此筆 |
| ③ | ★★ **正樣本**：出帳日 **2026-10-02** ＋ 截止日 **2026-11-10** | ✅ `raise_until` **→ 2026-11-17**、`due_date_source` **→ `STATEMENT`**；★ `account_limits.temp_end_date` **同步為 2026-11-17** |
| 單元測試 | | ✅ `limit-service` **69 測 0 失敗**（新增 `correctDueDate` 相關 **5**） |

### 7.14 商品參數消費閉環（CR-045 D4 首案）——2026-10-15 新增（`CR-045` D4／`#A-238`）

> **緣由**：`CR-045` P2 指出本服務（額度）**未消費任何商品參數**——`product-service` 之 `GET /api/v1/parameters/{ns}/{key}`（SCS-005 §6）生產端已備，且 `product.parameter.changed.v1` 事件已發布（`#A-130`），但**全庫無下游消費者**。依 `CR-045 §8` D1–D6 **已採納之立場**（2026-10-15 用戶授權 go）：首案以「商品額度策略」參數驅動本服務某一**既有多維度行為**，建立「**改權威參數值 → 額度行為改變**」之閉環。
> ★ **本節已實作並真機驗證（#A-239，2026-10-15）**：先補規格與驗收案例（spec-first），再實作 `limit-service` 之商品參數消費閉環，並以真機測試證明「改權威參數值 → 行為改變」。

**首案收斂（★ 不選 `limit.model`／`shared_scope`）**：該二鍵之多維度分層（account／card／family／corporate）為 **OPEN 待裁定（A1）**，行為語意尚未凍結，不宜做為第一條閉環（否則會「邊寫邊猜」）。故首案改選**已在 §7.2 完整定義、且可直接對應商品參數**之政策輸入：

> **首案鍵**：`namespace=product`、`paramKey=limit.approval-threshold-minor`（覆核門檻，分）。
> 對應既有行為：§7.2 之「增幅超過門檻 → 送 `PENDING_APPROVAL`，否則直接 `EFFECTIVE`」。現門檻為服務屬性 `cardcore.limit.approval-threshold-minor`（預設 500,000 分）→ **改由商品參數驅動**，即成「商品組合 × 額度」之最小化閉環。

**消費機制**：

- 新增 `domain/port/ProductParameterPort`（或 `ProductCapabilityPort`）：`getAsOf(namespace, paramKey, businessDate) -> ParameterValue`。
- 實作經 `GET /api/v1/parameters/{ns}/{key}`（帶 `businessDate` 取版本）**同步取得**（CR-045 §7.1：domain-owned synchronous API）。
- ★ **首案不引入快取**（CR-045 §5.3）：快取與事件失效僅於「快取實際存在且失效需求經量測」後落地；先以「改權威值 → 行為即變」證明閉環，避免「讀快取＝沒做」歧義。

**真機驗收（★ 沿用 §7.7／§7.9.7 之「改權威值 → 行為隨變」範式）**：

| # | 驗證 | 預期 |
|---|---|---|
| ① | 基線：現值 threshold＝500,000 分；申請增幅 600,000 分 | → `PENDING_APPROVAL`（現行行為） |
| ② | 改權威：`PUT /api/v1/parameters/product/limit.approval-threshold-minor`（value＝1,000,000，生效日＝今日） | 200；`CC_PRODUCT.PARAMETERS` 落新版本 |
| ③ | 同一申請（增幅 600,000） | → **直接 `EFFECTIVE`**（行為改變：門檻提高後未超過） |
| ④ | ★ **非快取證明**：繞過服務 `UPDATE CC_PRODUCT.PARAMETERS SET param_value=... WHERE param_key='limit.approval-threshold-minor'` | 下次取參即反映新值（若為快取則仍為舊值 → 證偽） |
| ⑤ | 版本：`businessDate` 取 `effective_from` 對應版本；舊版本區間不影響 | 行為隨生效日版本改變 |

> **★ 實作狀態（2026-10-15，`#A-239`）**：本首案**已實作並真機驗證閉環**（real Oracle via Testcontainers）。
> - **已自動化證明（驗收 ①＋③）**：`LimitAdjustmentServiceIntegrationTest$ProductParameterClosedLoop.paramValueChangeFlipsApprovalBehavior`——baseline `threshold=500,000` → 增幅 600,000 → `PENDING_APPROVAL`；改權威值 `threshold=1,000,000` → 同增幅 → **`EFFECTIVE`**（行為隨權威值翻轉）。證明「改權威參數值 → 額度行為改變」閉環成立。
> - **未取得自動化證明（誠實揭露，不虛報全閉環）**：驗收 ②（`PUT /api/v1/parameters/...` 改 product-service）、④（繞過服務直改 `CC_PRODUCT.PARAMETERS` 即反映＝非快取證明）、⑤（`businessDate` 版控）需 **product-service 參數寫入路徑與 `CC_PRODUCT.PARAMETERS` 表俱備**；本輪未建跨服務真機實驗（首案不引入快取、只讀取，且避免 mock／假造）。★ 這正是 `CR-045 §5.3`「不快取」之紀律：即便未做 ④，同步每次取參亦保證「下游改值 → 本服務下次取參即反映」。
> - **A4 fail-closed 已落地**：取參失敗（連線／4xx／5xx）轉 `CC-LIM-5001`，`LimitAdjustmentService.resolveApprovalThresholdMinor` 依 fail-closed **降級至預設門檻**（`cardcore.limit.approval-threshold-minor`，預設 500,000 分）並 `log.warn`，**不略過覆核檢核、不假造放行**。
> - **驗收 ②／④／⑤ 已閉合（2026-10-16，`#A-241`）**：於 `product-service` 新增 `ParameterClosedLoopIntegrationTest`（真 Oracle via Testcontainers ＋ 真 web 層）——**②** `PUT /api/v1/parameters/product/limit.approval-threshold-minor` 落新版本且現行版本自動封閉於 `newFrom − 1`；**⑤** `businessDate` 取昨日得舊值、今日得新值（生效日版控）；**④** 繞過服務直改 `CC_PRODUCT.PARAMETERS` 後再次取參即反映新值（**非快取證明**）；併同交易 Outbox 事件與 4 項負樣本（重疊 422 `CC-PRD-1002`／查無 404 `CC-PLT-4040`／空 body 400／缺 header 400）。★ **動工前查證更正前版註記**：「待 product-service 參數寫入路徑與 `CC_PRODUCT.PARAMETERS` 表俱備」**實為過度保守**——三者本地早已具備（`ParameterController` 之 `PUT`、`ParameterService` 版本鏈、`V1__baseline.sql` 之 `parameters` 表），②／④／⑤ **並非被外部阻塞**，僅缺跨服務真機實驗。
>   ⚠️ **根因更正（2026-10-16，`#A-241` 之原解釋有誤，現予修正）**：`#A-241` 當時將「嵌入式 Tomcat 無法以 `HttpClient` 連上」歸因於「沙箱 HTTP 攔截代理攔截迴路埠」——**該解釋不成立**。實測定位：**Tomcat 僅綁 IPv6 萬用字元，而 IPv4 迴路 `127.0.0.1`（`localhost` 優先解析至 IPv4）之連線會「掛住」而非「拒絕」**；改用 hostname 或 `[::1]` 立即 HTTP 200。→ 測試位址須用 **hostname**（或 `::1`），**不可用 `localhost`／`127.0.0.1`**。
> - **★ 已改為真 socket 閉環（2026-10-16，`#A-242`）**：根因既明後，`ParameterClosedLoopIntegrationTest` 改採**嵌入式 Tomcat（`server.port=0`）＋ JDK `HttpClient` 打真 socket**，路徑對映／`@RequestHeader` 綁定／`@Valid`／Jackson／`GlobalExceptionHandler` 錯誤碼→HTTP 映射**全部經由真實 HTTP 完成**，六例全通過。⚠️ **位址鐵則**：必須用 **hostname**（或 `[::1]`），**不可** `localhost`／`127.0.0.1`（見上方根因）。
> - **③之真 HTTP 段仍未閉合**：`limit-service` 之 `ProductParameterHttpClient` 經 socket 打到 `product-service` 之驗證需另立跨上下文測試；**根因既明（改用 hostname 即可），此項已無技術阻礙**，列為後續交付單元。

**OPEN（待裁定／待客戶輸入，不臆測）**：

- **A1**：`limit.model`／`shared_scope` 之 account／card／family／corporate 分層語意（D4 未決）→ 多維度額度閉環之先決。
- **A2**：同步取參 vs 事件消費（`product.parameter.changed.v1`）——首案採同步；事件消費僅於快取需失效時引入。
- **A3**：參數寫入授權（D6）——本首案**只讀取**，不開放本服務寫入；寫入 API 之 RBAC 待 D6 落地。
- **A4**：取參失敗之降級（`product-service` 不可達）→ **fail-closed**——**已落地（#A-239）**：依 CR-045 §7.14 A4 選「回預設門檻」（`cardcore.limit.approval-threshold-minor`，預設 500,000 分），`log.warn` 後降級、**不略過覆核檢核、不假造放行**；取參失敗拋 `CC-LIM-5001`（見 `SDD-006 §3.4`）。

> ⚠️ **不逾越邊界**：不恢復 stale endpoint（D2）、不臆測行銷活動政策（D3）、不把 `product.rule_sets` 升格為活動模型（D3）。

### 7.15 載體（卡）級主額度——額度主體**維度化**（`CR-053` D-1）——2026-10-17 新增（`#A-285`）

> **緣由**：`CR-052 §4.2` BEAM PMO 裁定 **C-1 由 (i) 修正為 (ii)「卡片有獨立主額度」**（客戶要求，
> RFP `FR.md §2.4(1)`「歸戶、持卡人、**卡片**調額／查詢」）。原模型 `account_limits` 為
> **帳戶層**（`V10` 明示「額度非以卡為單位」），與客戶要求衝突 → 開立 `CR-053` 承載資料模型。
> BEAM PMO 2026-10-17 授權「架構自主推進」→ 本節依 `CR-053` **建議立場**（D-1～D-5）落地第一批。

#### 7.15.1 資料模型（`V11__limit_subject_dimension.sql`）

| 欄位／約束 | 內容 |
|---|---|
| `subject_type` | 額度主體維度：`HOUSEHOLD`／`ACCOUNT`／`PARTY`／`CARD`／`CORP_SUB`；既有列＝`ACCOUNT` |
| `subject_id` | 主體識別：`ACCOUNT` → `account_id`；`CARD` → `instrument_id`（`V10` 載體識別） |
| `account_id` | **保留**——卡片列仍記錄所屬帳戶（供歸戶彙總與稽核） |
| 唯一鍵 | 由 `(account_id, currency)` 改為 **`(subject_type, subject_id, currency)`**；原鍵在「同帳戶多卡」下必然衝突 |
| 自動補值 | `BEFORE INSERT` trigger：`subject_type='ACCOUNT'` 且 `subject_id IS NULL` → 補為 `account_id`；**其他維度不推導**（缺值即失敗） |

> ★ **為何用 trigger 而非直接 `NOT NULL`**（實測教訓）：既有 51 處寫入（事件處理與測試）
> 只帶 `account_id`；直接設 `NOT NULL` 會使**既有功能全數寫入失敗**（實測 51 個測試轉紅）。
> trigger 使「ACCOUNT 維度之主體即帳戶」之語意**單一且明確**，相容既有路徑且不產生 NULL 維度。

#### 7.15.2 查詢語意（`GET /api/v1/instruments/{instrumentId}/limits`）

**★ 路徑用 `instruments` 而非 `cards`**：本服務只認交易載體（`V10`：實體卡／虛擬卡號／
Token 同構）；`card→instrument` 對映不在本服務（`CR-052 C-5` 實查：無直呼路徑），
以 `cardId` 猜 `instrumentId` 即臆造。對外 UI 之「卡片」由 BFF 對映。

**可用額度口徑（★ 保守下界，非精確值）**：

```
可用 = 本卡（永久 + 有效臨時 + 彈性）− 帳戶已用 − 本卡可歸屬佔用      （下限 0）
```

| 分量 | 來源 | 說明 |
|---|---|---|
| 本卡額度 | `CARD` 維度列 | 永久／臨時（有效期內）／彈性 |
| 帳戶已用 | `ACCOUNT` 維度列 `used_amount` | **權威**；非本卡專屬 |
| 本卡佔用 | `limit_holds` where `instrument_id=?` and `status='HELD'` | **僅可歸屬者** |
| `holdAttribution` | 該帳戶是否存在 `instrument_id IS NULL` 之 `HELD` 佔用 | `FULL`／`PARTIAL` |

> ★ **為何不採「本卡已用」**：已入帳交易不帶 `instrument_id`，歷史佔用亦不得回填
> （`V10`／`CR-053` D-4）→ 卡片級已用**無權威來源**。以帳戶已用扣減屬**保守**
> （不會超額放行），但**非精確**；精確分攤待 `CR-052 C-2` 投影落地。
> 回應帶 `holdAttribution=PARTIAL` 如實揭露，**不得**據此宣稱精確可用額度。

**★ 查無即拒絕（不回退帳戶額度）**：未設 `CARD` 維度主額度時拋 `CC-LIM-1001`，
**不以歸戶額度冒充卡片額度**（`CR-050 §2.1` 同類原則）。

#### 7.15.3 守界

* **不臆造維度歸屬**：`HOUSEHOLD`／`PARTY`／`CORP_SUB` 為模型預留，現行**無寫入路徑**；
  指定時查無主檔即 fail-closed（企業子額度之隸屬與分攤待客戶口徑，`CR-043 §10.5`）。
* **不由既有帳戶額度複製卡片額度**（`CR-053` D-4）——卡片主額度初始值須由客戶提供。
* **不改既有帳戶層行為**：`restoreUsedAmount`／`reapply`／調額等寫入一律限定
  `subject_type='ACCOUNT'`（否則會污染卡片列，且「恰 1 列才更新」之保守規則會被
  多維度列誤判為多幣別而**永久停止更新**）。
* **卡片級調額（寫入）為後續批次**（`CR-052` C-3）：`LIMIT_ADJUSTMENTS` 現為帳戶維度，
  擴充需另立遷移與流程，本批不放置半套端點。

#### 7.15.4 驗收（真機）

| # | 驗證 | 結果 |
|---|---|---|
| ① | `V11` 回填：既有列 `subject_type='ACCOUNT'`、`subject_id=account_id`，無 NULL 維度 | ✅ |
| ② | 同帳戶多卡並存（原唯一鍵會衝突）；同（維度, 主體, 幣別）重複則拒 | ✅ |
| ③ | 可用額度＝手算值（含臨時額度有效期、負值截斷至 0） | ✅ |
| ④ | 查無 `CARD` 主額度 → `CC-LIM-1001`，**不回退**帳戶額度 | ✅ |
| ⑤ | `holdAttribution`：全帶 `instrument_id` → `FULL`；存在歷史佔用 → `PARTIAL` | ✅ |
| ⑥ | **回歸**：同帳戶有 `CARD` 列時，`restoreUsedAmount`／`applyAdjustedAmount` 僅動 `ACCOUNT` 列 | ✅ |

### 7.16 卡片級額度**調額寫入**（`CR-052` C-3）——2026-10-17 新增（`#A-286`）

> **緣由**：`CR-052 §4` BEAM PMO 裁定 **C-3 含額度寫入**（非僅查詢）；`CR-053` D-1 之維度化
> （§7.15）已備資料基礎，本節補**寫入路徑**。
> 沿用既有 §7.2 覆核門檻與 `SDD-005 §8` 狀態機——**不另立流程**（避免兩套語意）。

#### 7.16.1 請求維度之決定

| 請求 | 維度 | 主檔 |
|---|---|---|
| 帶 `instrumentId` | **`CARD`**（`subject_id`＝instrumentId） | 該卡之 CARD 維度列 |
| 未帶 | `ACCOUNT`（既有行為，**完全不變**） | 該帳戶之 ACCOUNT 維度列 |

* `beforeAmount` 取**該維度**之主額度（`PERMANENT`／`TEMPORARY`／`SHADOW` 對應欄位）；
  `SPECIAL` 仍走專用額度池（`§7.9.3`），**不因本節改變**。
* ★ **不得混用維度**：卡片級調額**只**改 CARD 列，**不得**連動帳戶列（反之亦然）。
  （維度化後同帳戶多列，任何以 `account_id` 定位的寫入都會跨維度污染——`§7.15.3` 之教訓。）

#### 7.16.2 無既有 CARD 列時：**核准後建立**（客戶提供初始值之唯一途徑）

| 情境 | 處理 |
|---|---|
| 該卡尚無 CARD 列 | `beforeAmount = 0`；核准（或未達門檻直接生效）時**建立** CARD 列，`permanent_amount = afterAmount` |
| 已有 CARD 列 | 照常調整（`UPDATE ... WHERE subject_type='CARD' AND subject_id=?`） |

> ★ **這不是「臆造」**：值由呼叫方**明確提供**（`targetAmount`），且 `CR-053` D-4 所禁者為
> 「**由既有帳戶額度複製**出卡片額度」。若無此路徑，新卡將**永遠無法**設定卡片額度（死路）。
> ★ 建立時之 `account_id` 由請求帶入（與帳戶級調額相同），本服務**無** `card→instrument`
> 對映能力（`CR-052 C-5`），故**歸屬未經驗證**——須由呼叫端（BFF／card-service）保證，
> 並於 API 文件與本節**如實揭露**。

#### 7.16.3 ★ 事件輸出之守界（**重要**）

`limit.limit.adjusted.v1` 之 payload 為「**帳戶總額度**」（`§7.5`），授權端據以更新讀模型
`account_limit_caches`。故：

* **`ACCOUNT` 維度** → 照舊發射（行為不變）。
* **`CARD` 維度** → **不發射** 該事件。理由：卡片額度若以帳戶事件送出，將使授權讀模型之
  「帳戶總額度」被**卡片額度覆蓋** → 授權可用額度**嚴重失真**（超額放行或誤拒）。
* 卡片額度之授權取值走**即時查詢**（`§7.15.2`），**不經**該讀模型。
* ⚠️ 若日後需事件化，應另立 `limit.instrument.adjusted.v1` 並先凍結 payload
  （`SAD-004 §5.3`）——**本節不臆造事件**。

#### 7.16.4 驗收（真機）

| # | 驗證 | 預期 |
|---|---|---|
| ① | 卡片級調額（無 CARD 列）→ 核准後 | 建立 CARD 列、`permanent = after`；**ACCOUNT 列不變** |
| ② | 卡片級調額（已有 CARD 列）→ 直接生效（未達門檻） | 僅 CARD 列更新 |
| ③ | 帳戶級調額（既有路徑回歸） | 僅 ACCOUNT 列更新；**CARD 列不變** |
| ④ | 卡片級調額是否發 `limit.limit.adjusted.v1` | **不發**（`CC_LIM.OUTBOX` 無新增列） |
| ⑤ | 卡片級調額超過覆核門檻 | `PENDING_APPROVAL`（與帳戶級同門檻） |

### 7.17 共享池（共用額度）權利與**卡片上限檢核**（`CR-055` P4-1）——2026-10-17 新增

> **口徑來源**：BEAM PMO 2026-10-17（`CR-055 §1` 逐字保留）。
> ★ 一句話：**共享池＝總額度的另一面**（共用額度），**不是**額外池。

#### 7.17.1 三個概念（★ 不可混用）

| 概念 | 定義 | 承載 |
|---|---|---|
| **總額度** | 客戶在本行之共用上限；**≠ 各卡額度之和** | `account_limits`（`ACCOUNT` 維度） |
| **卡片名目額度** | 核給該卡的金額 | `account_limits`（`CARD` 維度） |
| **共享池權利** | 該卡**是否可動用總額度** | `account_limits.shared_pool_eligible`（`V13`） |

> ★ 「總額度 5 萬、三張卡 5／2／3 萬」→ 共用額度**仍為 5 萬**（不是 10 萬）。
> 2 萬那張若享共享池，**最多可刷到 5 萬**（不是 2＋5＝7 萬）。

#### 7.17.2 可刷上限（授權 `hold()` 之檢核，**風控底線**）

```
享共享池：可刷上限 = 總額度 − 帳戶已用 − 帳戶全部未釋放佔用   （＝帳戶可用）
不享共享池：可刷上限 = 卡片名目額度 − 該卡未釋放佔用          （硬上限）
```

★ 不享共享池者，**卡額度與帳戶可用取較嚴者**（兩者都要過）。
★ **無 CARD 維度列時不檢核**（沿用帳戶額度）——向後相容：既有卡未設定卡片額度前行為不變。

**★ 修正之真實缺陷**：原 `hold()` **完全不看卡片名目額度**（CARD 維度僅供查詢）→
「不享共享池」的卡會用到總額度（例：總額度 5 萬、卡額度 2 萬 → 可刷到 5 萬＝**超放 3 萬**）。

#### 7.17.3 查詢呈現（★ 兩個數字，不可相加）

| 欄位 | 語意 |
|---|---|
| `availableAmount` | **卡片自身可用**＝卡名目額度 − 該卡佔用（**不扣帳戶已用**） |
| `sharedPoolEligible` | 是否享共享池 |
| `sharedPoolAvailableAmount` | 可動用**總額度**之剩餘（＝帳戶可用）；★ 僅 `eligible=true` 有值，否則 `null` |

> ★ BEAM PMO：「其**卡片級的『可用』仍是他原本的額度**」——故 `availableAmount` **不扣帳戶已用**
> （此為 `CR-055` 對 `SCS-002 §7.15.2` 舊「保守下界」口徑之**修正**）。

#### 7.17.4 參數（商品級）與快照

| 參數鍵（`namespace=product`） | 值域 | 說明 |
|---|---|---|
| `limit.shared-pool-eligible` | `true`／`false` | 該商品之卡是否可動用總額度；**缺值 fail-closed** |

★ **參數 → 卡片快照**：設定卡片額度時由呼叫端帶入 `sharedPoolEligible` 並落地於 CARD 列；
事後改參數**不改變**已設定之卡片語意（留痕原則）。
⚠️ 本服務**無** `card→product` 對映（`CR-052 C-5`），故不自行查商品參數。

#### 7.17.5 驗收（真機）

| # | 驗證 | 預期 |
|---|---|---|
| ① | 不享共享池、卡額度 200,000、帳戶 1,000,000 → 刷 150,000 後再刷 100,000 | **拒絕**（`CC-LIM-1001`，訊息含「不享共享池」） |
| ② | 享共享池、卡額度 200,000 → 刷 800,000（≤ 總額度） | **通過** |
| ③ | 無 CARD 列（既有卡）→ 刷 900,000 | **通過**（向後相容） |
| ④ | 不享共享池、卡額度足但帳戶只剩 50,000 → 刷 100,000 | **拒絕**（帳戶可用不足） |

### 7.18 預借現金額度（`CR-055` P4-2）——2026-10-17 新增（`#A-296`）

**口徑（權威：`CR-055 §2.5`）**：

```
預借上限 = 總額度（永久＋有效臨時＋彈性） × ratio / 100     （向下取整）
預借可用 = min(預借上限 − 已預借, 有效可用)                  （下限 0）
```

★ **不是獨立額度**：總額度 5 萬、ratio 80% → 上限 4 萬，**總額度仍 5 萬**（非 9 萬）。
★ 與一般消費**共用**同一總額度 → 預借佔用**亦計入**帳戶佔用。

#### 7.18.1 資料模型（`V15`／`V16`）

| 變更 | 內容 |
|---|---|
| `V15` | `account_limits.cash_advance_ratio NUMBER(3)`（可空、`CHECK 0–100`）＋`limit_adjustments.cash_advance_ratio`（留痕） |
| `V16` | **`limit_holds.hold_type` 值域加入 `CASH_ADVANCE`**——`V1` 原值域只有 `GENERAL`／`SPECIAL`／`INSTALLMENT`，真機第一次執行即 `ORA-02290`（見 §7.18.5） |

#### 7.18.2 `null` ≠ `0`（**fail-closed**）

| 值 | 語意 | 行為 |
|---|---|---|
| `NULL` | **未設定** | 預借**一律拒絕**（`CC-LIM-1001`，訊息「預借現金未開放…未設定預借現金比例」）；查詢四欄皆 `null` |
| `0` | **明確禁止** | 上限 0、可用 0；預借走「額度不足」（訊息與「未開放」可區分） |
| `1–100` | 已設定 | 依上式計算 |

★ **不臆造預設比例**（80% 只是 `CR-055` 之舉例，非規範）。

#### 7.18.3 「有效可用」取較嚴者

| 卡之共享池權利 | 有效可用 |
|---|---|
| 享共享池（`shared_pool_eligible=1`） | **帳戶可用**（總額度−帳戶已用−帳戶全部佔用） |
| 不享共享池（`=0`） | **卡片自身可用**（卡名目額度−該卡佔用） |

★ 若一律用帳戶可用，不享共享池之卡即可用預借**繞過卡片硬上限**（`§7.17.2` 之缺陷以另一形式復活）。

#### 7.18.4 授權檢核（`hold(holdType='CASH_ADVANCE')`）

順序：① 卡片上限（不享共享池時）→ ② **預借子上限** → ③ 帳戶可用。
②③ **兩者皆須通過**（共用同一總額度）。

★ `holdType` 由**呼叫端**帶入——本服務不判斷「這筆是不是預借現金」。

#### 7.18.5 ★★ 實作期攔下之真實缺口（值域未涵蓋）

`V1` 之 `ck_holds_hold_type` 值域 `('GENERAL','SPECIAL','INSTALLMENT')` **無** `CASH_ADVANCE`
→ 真機測試第一次執行即 `ORA-02290: 違反檢查限制條件`。
**這不是測試寫錯，是語意與 DDL 不一致**：「已預借」唯一可識別的位置就是 `hold_type`。

**修法**：`V16` 以 `user_constraints`（`search_condition_vc LIKE '%hold_type%'`）**動態定位**
系統命名約束後卸除，再以**具名** `ck_holds_hold_type` 重建。
★ **不改成「無 CHECK」**——值域即行為契約，放寬會讓打錯字靜默變成新型別。

#### 7.18.6 驗收（真機；Testcontainers 真 Oracle＋真 Flyway）

`CashAdvanceLimitIntegrationTest` **7 例**：未設定拒絕／80% 上限 160,000 與可用 60,000／
預借佔用計入帳戶佔用（`holdAmount=100,000`、`available=900,000`）／超上限拒絕（訊息帶上限）／
`0` 與 `null` 可區分／不享共享池取卡片可用（40,000 vs 30,000 兩例）／調額帶入比例落地。

---

### 7.19 企業子額度與組織樹（`CR-055` P4-3）——2026-10-17 新增（`#A-296`）

**口徑（權威：`CR-055 §2.6`）**：

```
上層可用 = 上層總額度 − 上層已用 − 上層自身佔用 − **所有下層佔用之和**（下限 0）
```

#### 7.19.1 資料模型（`V17__corp_units.sql`）

| 表／欄 | 內容 |
|---|---|
| `corp_units` | `id`／`unit_no`（唯一）／`unit_name`／`parent_unit_id`（根為 `NULL`）／`root_unit_id`／`depth`／`settlement_account_id`／`status` |
| `limit_holds.corp_unit_id` | 佔用歸屬企業單位（可空）；與 `instrument_id` **並存不互斥** |
| 額度列 | `account_limits`：`subject_type='CORP_SUB'`、`subject_id = corp_units.id` |

**★ 為何獨立一張表**：① 組織樹是主體結構、額度是掛在結構上的數字，生命週期不同；
② 同一單位可能有多幣別額度列（唯一鍵含 `currency`），樹只能有一份。

**★ `settlement_account_id` 為何必要**：`account_limits.account_id` 為 `NOT NULL`，
企業額度歸屬**企業戶結算帳戶**（由呼叫端提供、子單位繼承、**不臆造**）。

#### 7.19.2 三個容易做錯的點

| 陷阱 | 後果 | 做法 |
|---|---|---|
| 只檢核**自身**可用 | N 個子單位各花光 → 上層**超放 N 倍** | 檢核走**上層鏈**（`CONNECT BY PRIOR parent_unit_id = id`），任一層不足即拒絕，並指出**不足之層** |
| 把自身佔用算進「下層之和」 | 同一筆被扣**兩次** → 可用低估一倍 | `corpUnitHolds` 與 `descendantCorpHolds` **分開**；下層**不含自身** |
| 遞迴方向寫反 | `CONNECT BY` 回**空集合且不報錯** → 靜默低估 | 以真機三層樹測試鎖住（根＝750,000） |

#### 7.19.3 端點

| 端點 | 說明 |
|---|---|
| `POST /api/v1/corp-units` | 建根／子單位（`unitNo` 冪等；已存在回既有、不覆寫） |
| `GET /api/v1/corp-units/{unitId}/limit` | 額度查詢：**同時回自身佔用與下層佔用**（只回總數無法解釋「上層為何變少」） |
| `PUT /api/v1/corp-units/{unitId}/limit` | 設定總額度（**風控核定**端點，非客戶申請路徑） |
| `POST /api/v1/corp-units/{unitId}/holds` | 建立佔用（上層鏈檢核） |

#### 7.19.4 守界

* **不發射** `limit.hold.created.v1`：該事件消費端語意為**卡片授權佔用**（payload 無企業單位維度），
  照發會讓投影出現「無載體佔用」→ 污染卡片級可用額度。企業額度以本服務**即時查詢**為權威。
* **企業額度「申請→覆核」未接**：`LimitAdjustmentService` 目前僅由 `instrumentId` 判斷 CARD 維度。
* **停用端點未做**（`status='INACTIVE'` 值域已在 DDL）。

#### 7.19.5 驗收（真機；真遞迴 SQL）

`CorpLimitIntegrationTest` **6 例**：三層樹 depth／root／歸屬帳戶繼承／
子佔用扣減上層（分公司 50,000、根 750,000）／上層不足即拒（訊息指出不足之層 `B-003`）／
未核定額度 fail-closed／設定額度後可佔用／重複 `unitNo` 不覆寫。

### 7.20 企業卡改綁（`CR-058` C）——2026-10-18 新增（`#A-301`）

**語意（權威：`CR-058 §2`）**：**改綁改的是「未來歸屬」，不是「過去帳」**。
既有未釋放佔用保留原 `corp_unit_id`（forward-only），新佔用歸新單位。

#### 7.20.1 資料模型（`V20__corp_unit_instrument_history.sql`）

| 表 | 欄位 | 說明 |
|---|---|---|
| `corp_unit_instrument_history` | `id`／`instrument_id`／`from_unit_id`（可空）／`to_unit_id`／`reason`／`changed_by`／`changed_at`＋`created_at`／`updated_at`／`version` | 只記錄歸屬變更；**不搬移** `limit_holds` |

`corp_unit_instruments` 仍以一卡一單位（`instrument_id` PK）表達**現行**歸屬。

#### 7.20.2 埠與服務

```java
// CorpUnitRepository
Optional<UUID> rebindInstrument(UUID targetUnitId, UUID instrumentId,
                                String reason, String changedBy);

// CorpLimitService
RebindView rebindInstrument(UUID unitId, UUID instrumentId,
                            String reason, String changedBy, String currencyCode);
```

| 情境 | 行為 |
|---|---|
| 目標單位不存在／停用 | `CC-LIM-1003` |
| `reason` 空 | `CC-LIM-1003`（稽核必填） |
| instrument 未綁定 | `CC-LIM-1003`（導引先 `bind`） |
| 目標＝現有單位 | **冪等**，不更新、不寫歷史 |
| 目標≠現有單位 | 更新 `corp_unit_instruments` ＋ 寫一列歷史（同一交易） |

#### 7.20.3 API

`POST /api/v1/corp-units/{unitId}/instruments/{instrumentId}/rebind`

```json
{ "reason": "組織調整", "changedBy": "ops-1", "currency": "TWD" }
```

回應：`unitId`／`instrumentId`／`fromUnitId`／`toUnitId`／`openHoldsAtOldUnit`／`changedAt`。
`openHoldsAtOldUnit` 為原單位**未釋放佔用**（改綁不搬移，供維運判斷）。

#### 7.20.4 驗收

`CorpLimitIntegrationTest` 新增 3 例：forward-only（舊佔用留原單位、新佔用歸新單位、歷史 1 列）／
同單位冪等（歷史 0 列）／未綁定拒絕。

---

### 7.21 企業額度申請→覆核（`CR-058` D）——2026-10-18 新增（`#A-301`）

**語意（權威：`CR-058 §3`）**：企業額度走**同一狀態機**（`limit_adjustments` 已支援 `CORP_SUB`，`V12`）。

#### 7.21.1 主體與歸屬

| 欄位 | 值 |
|---|---|
| `subject_type` | `CORP_SUB` |
| `subject_id` | `corp_units.id` |
| `account_id` | `corp_units.settlement_account_id`（由單位解析，不臆造） |
| `adjust_type` | `PERMANENT` |

#### 7.21.2 服務（`CorpLimitAdjustmentService`）

```java
AdjustmentView apply(UUID unitId, String currency, long targetAmountMinor,
                     String reason, String applicantId);        // 申請（走門檻）
AdjustmentView applyDirect(UUID unitId, String currency, long targetAmountMinor,
                           String reason, String operatorId);   // 風控直接核定（留審計）
```

- 門檻：`product/limit.approval-threshold-minor`；取參失敗降級組態預設 500,000（fail-closed）。
- 直接核定以「極大門檻」送出 → 狀態 `EFFECTIVE`，但**仍寫一筆 `limit_adjustments`**。
- `CORP_SUB` **不發射** `limit.limit.adjusted.v1`（payload 為帳戶總額度，照發會污染授權讀模型）。

#### 7.21.3 API

| 端點 | 用途 |
|---|---|
| `POST /api/v1/corp-units/{unitId}/limit-adjustments` | 申請（`targetAmount`／`currency`／`reason`／`applicantId`） |
| `POST /api/v1/limit-adjustments/{id}/approval` | 覆核（既有端點） |
| `PUT /api/v1/corp-units/{unitId}/limit` | 風控直接核定（**留 EFFECTIVE 軌跡**） |

#### 7.21.4 職責分離

`LimitAdjustmentService.approval`：當 `subjectType=CORP_SUB` 且 `APPROVE` 且
`approverId == applicantId` → `CC-LIM-3001`（**僅對 CORP_SUB 生效**，不改既有維度行為）。

#### 7.21.5 驗收

`CorpLimitAdjustmentIntegrationTest` 4 例：未超門檻直接生效／超門檻待覆核且核准後才套用／
申請人不得自核／直接核定留審計。

---

### 7.22 Stand-in 上傳載體與型態（`CR-058` E）——2026-10-18 新增（`#A-301`）

**語意（權威：`CR-058 §4`）**：授權當下已知 `instrumentId`／`holdType`，就必須保存並上送。

#### 7.22.1 資料模型（`V15__limit_holds_hold_type.sql`）

| 變更 | 內容 |
|---|---|
| `V14`（既有） | `limit_holds.instrument_id` |
| `V15`（本批） | `limit_holds.hold_type VARCHAR2(32 CHAR)`（可空） |

兩者**可空**：舊資料不得臆造；上送時 `instrumentId=null` 照送、`holdType` 預設 `GENERAL`。

#### 7.22.2 模型與埠

```java
record PendingStandIn(String authNo, UUID authorizationId, UUID accountId,
                      UUID instrumentId, long amountMinor, String currency, String holdType) {}

record StandInAdviceState(String authNo, UUID authorizationId, UUID accountId,
                          UUID instrumentId, long amountMinor, String currency,
                          String holdType, Instant uploadedAt) {}

// LimitUploadPort
UUID uploadHold(UUID accountId, UUID instrumentId, UUID authorizationId,
                Money amount, String holdType);
```

#### 7.22.3 查詢與寫入

- `JdbcLimitPort.HOLD_SQL` 寫入 `instrument_id`／`hold_type`（Stand-in 代佔即保留）。
- `PENDING_STANDIN_SQL`／`STANDIN_ADVICE_SQL` 以**純量子查詢**由 `limit_holds` 取最近一筆
  `instrument_id`／`hold_type`（一授權一佔用；避免 `MAX(RAW)` 不支援）。
- `RemoteLimitUploadAdapter` 以 `HoldRequest.of(...)` 帶載體與型態（與即時授權一致）。

#### 7.22.4 相容

| 情境 | 行為 |
|---|---|
| `instrumentId` 有值 | 上送帶載體；權威端 `INSTRUMENT_ID` 落地 |
| `instrumentId=null`（舊資料） | 上送 null；僅歸帳戶；`holdAttribution=PARTIAL` |
| `holdType=CASH_ADVANCE` | 觸發預借子上限檢核 |
| `holdType` 缺值 | 正規化 `GENERAL` |

#### 7.22.5 驗收

`StandInUploadServiceTest`：後送帶 `instrumentId`／`holdType` 之斷言；
`AuthorizationRepositoryIntegrationTest`：`findPendingStandIn` 由 `limit_holds` 取載體／型態。

---

## 8. 外部依賴

| 依賴 | 用途 |

| Oracle `CC_LIM` | 權威儲存 |
| `common-saga`（僅用其狀態機引擎） | 狀態轉換檢核 |

---

## 9. 測試案例

| 編號 | 情境 | 結果 |
|---|---|---|
| UT-LIM-01 | 歸戶額度查詢（永久） | 10,000,000 |
| UT-LIM-02 | 含臨時額度查詢 | 150,000 |
| UT-LIM-03 | 佔用 300,000 | 可用 9,700,000 |
| UT-LIM-04 | 釋放 | 可用回到 10,000,000 |
| UT-LIM-05 | 佔用超過可用 | 拒絕，`CC-LIM-1001`，不寫入佔用 |
| UT-LIM-06 | 調整申請（未超限） | `EFFECTIVE` + 主檔更新 |
| UT-LIM-07 | 調整申請（超限） | `PENDING_APPROVAL` |
| UT-LIM-08 | 覆核駁回 | `REJECTED` |
| UT-LIM-09 | 覆核通過 | `EFFECTIVE` + 主檔更新 |
| UT-LIM-10 | 對終態再覆核 | `409` / `CC-LIM-3002` |
| UT-LIM-11 | 撤銷 | `REVOKED` + 主檔回復 |
| UT-LIM-12 | 臨時額度到期排程 | `EXPIRED` + 歸零 |
| UT-LIM-13 | 專用額度申請（未超限，`purpose=TAX`） | `EFFECTIVE` + `special_limits` 新增 `ACTIVE` |
| UT-LIM-14 | 專用額度同用途再次申請 | **更新**既有 `ACTIVE`（**不新增列**） |
| UT-LIM-15 | 專用額度 `purpose` 不在值域 | 422 / `CC-LIM-1003`，**不落庫** |
| UT-LIM-16 | 專用額度撤銷 | `REVOKED` + `special_limits.status = CANCELLED` |
| UT-LIM-17 | 專用額度到期排程 | `ACTIVE` 且 `effective_to < 今日` → `EXPIRED` |
| UT-LIM-18 | 查詢歸戶專用額度 | 僅回 `ACTIVE`；**不含** `PENDING_APPROVAL` |
| UT-LIM-19 | 適用性：MCC 命中白名單 | `applicable=true`；不命中 → `false`（`§7.10.1`） |
| UT-LIM-20 | 適用性：多筆命中 | **有明確 `scopeRef` 者優先**（決定性） |
| UT-LIM-21 | 專用池佔用 | **不觸碰卡人額度**；池內不足 → `CC-LIM-1003` 且不留佔用 |
| UT-LIM-22 | 專用池釋放 | `used_amount` **對稱回沖**；一般佔用不動專用池 |
| UT-LIM-23 | 8-6：非專用額度之請款 | **不觸發**（不調高、不落紀錄） |
| UT-LIM-24 | 8-6：專用額度之請款 | 釋放佔用 ＋ 調高 ＋ 落紀錄（迄日＝入帳日＋32） |
| UT-LIM-25 | 8-7：FIFO 回收 | 先調高者先回收；回收完畢轉 `CLOSED` |
| UT-LIM-26 | 8-7：**只扣回收額** | **不得**歸零（保留人工核准之臨時額度） |

---

## 10. 已知限制

1. **覆核門檻為金額模擬**，非角色／權限系統。 |
2. ~~**`SPECIAL` 調整類型未支援**~~ → ✅ **已解除（2026-10-02，`CR-014 v1.1`／§7.9）**：
   `SPECIAL` 之寫入（`upsertActive`）、撤銷（`cancelActive`）、到期（`EXPIRED`）與查詢（改讀 `special_limits`）皆已實作。
   ⚠️ **仍未實作者**：`8-2`（角色／客層上限）、`8-3`／`8-5` 之**交易時扣抵與超額拒絕**（屬 `authorization-service`）、
   `8-6`／`8-7`（臨時額度串動）、`scope_ref`（MCC 用途範圍）——見 #12～#15。 |
3. **`used_amount` 之維護**：原載「不由此服務維護（應由帳務服務更新）」——✅ **部分解除（`#A-124`）**：
   現由本服務**消費 `payment.payment.posted.v1`** 遞減之（`§7.7`，`SAD-002 §3.2` 之明文設計）。
   ⚠️ **仍未由帳務事件維護**——`posting.transaction.posted.v1`（`SAD-002 §3.2` 之「釋放佔用」）**尚未實作**，
   故「帳務入帳使 `used_amount` **增加**」之路徑**不存在**（目前僅有繳款使之**減少**）。
   亦即 `used_amount` 現行實務上**只減不增**，需靠人工／種子資料建立初始值。 |
4. **監控未接**。 |
5. **`limit.limit.adjusted.v1` 之發射僅涵蓋「已生效」路徑**（`§7.5`）：**撤銷**（`§7.3` 回復額度）與**臨時額度到期**（`§7.4` 歸零）**尚未發射事件**——惟此兩者**同樣改變額度**，故下游讀模型於該兩情境**不會更新**（**已知落差**，待後續補）。 |
6. **`sequenceNo` 為同 JVM 內遞增之替代序號**（`§7.5`）：多實例部署可能重複；正式應改帳戶層級持久化序號。 |
7. **`eventId` 為隨機 UUID，非 `SAD-004 §5.1` 所載之 ULID**（同 `#A-118` 之既有實作）；冪等語意不受影響（僅需唯一），惟格式與規格文字不一致，待統一。 |
8. **`limit.hold.*` 之消費者未實作**（`§7.6`，`#A-122`）：✅ **訂閱關係已化解（2026-10-01，`CR-028 §3 #2／#3`）**——已確認 `authorization-service` **同時訂閱** `limit.hold.created.v1` 與 `limit.hold.released.v1`（並補登至 `SAD-004 §5.2`）；**惟消費端程式仍未實作**（屬批次 B，`CR-028 §6`）。 |
9. ~~**`limit.hold.created.v1` payload 缺 `authorizationId`**~~ → ✅ **已化解（2026-10-01，`CR-028 §3 #4`）**：補為**可選**欄位（依 `SAD-004 §5.3` **不升版**）；證據：`CC_LIM.LIMIT_HOLDS` 與 `CC_AUTH.LIMIT_HOLDS` **皆有 `AUTHORIZATION_ID`**。**實作補入屬批次 B**。 |
10. **`payment.payment.posted.v1` payload 缺 `currency`**（`§7.7`，`#A-124`）：消費端之額度主檔鍵為 (`account_id`, `currency`)，**無法精確定位**——現行保守規則為「該帳戶**恰一列**才更新；多幣別則**告警不更新**」（**不臆測幣別**）。已列 `CR-027 §5 #10` **待裁定**（建議補為**可選**欄位）。 |
11. **`UNMATCHED`（重新佔額）／`REVERSED` 之事件未實作**（`§7.7`，`#A-124`）：`SCS-008 §8.2` 之轉換 6 會將 `LIMIT_RESTORED` 設回 `false`（**撤銷額度恢復**）、沖正亦然，惟**兩者皆不發射事件** → **「繳款後又轉未匹配／沖正」時，額度不會被重新佔用**（**已知落差**）。已列 `CR-027 §5 #9` **待裁定**。 |
12. ~~**專用額度之交易時扣抵未實作**~~ → ✅ **已解除（2026-10-02，`CR-035 §2.2`／`§7.10`）**：`8-3`（走專用池、**不佔卡人額度**）、`8-4`（無適用則回歸卡人額度）、`8-5`（兩池皆不足 → 拒絕）皆已實作並真機驗證（`§7.10.4`）。授權端經 `SpecialLimitPort` 即時查詢本服務之適用性端點。⚠️ **仍未實作**：`8-6`／`8-7`（臨時額度串動；Phase 3）。 |
13. ~~**`scope_ref`（用途範圍，如特定 MCC）未實作**~~ → ✅ **已實作（2026-10-02，`CR-035 §2.1`）**：`special_limits.scope_ref`（MCC 白名單，逗號分隔）＋ `limit_adjustments.scope_ref`（軌跡）；`NULL` ＝ 不限定。`V7` 遷移。真機驗證見 `§7.10.4` 之 ①～③。 |
14. ~~**`8-2`（角色／客層上限）未實作**~~ → ✅ **已實作（2026-10-02，`CR-035 §2.3`／`§7.11`）**：
    新增 `special_limit_policies`（`V8`）＋ `CallerIdentityPort`（★ **角色自權杖**）＋ 政策檢核（fail-closed）＋ 2 個維護端點。
    ★ **折衷之處理**：**角色之折衷已移除**（改自**已驗證之權杖**，不接受請求帶入）；
    ⚠️ **客層仍由請求帶入**（附四項證據，見 `§7.11.3`）。
    ⚠️ **政策維護端點之權限未收斂**（任何已驗證呼叫者皆可設定上限）——列後續。 |
15. **`limit_adjustments` 與 `special_limits` 之間無外鍵**（`§7.9`）：對應關係為業務鍵 `(account_id, currency, purpose)`；「哪一筆調整產生了現行定義」須由兩表**時序推導**，非精確可稽核。 |
16. ~~**`used_amount` 之語意為「佔用中」而非「已入帳未清償」**~~ → ✅ **已收斂（2026-10-02，`CR-035 §2.4`／`§7.12`）**：請款入帳（`posted.v1`）**會釋放**該專用佔用 → 佔用可回落。⚠️ **仍未實作**：請款後之「**已入帳未清償**」不再以專用池表達（改由 8-6 之**臨時額度調高**表達），故專用池之 `used_amount` 於請款後即歸零——**與「已清償」無關**，此為**設計選擇**（專用池管「額度」、臨時額度管「週轉」）。
17. ~~**8-6 之繳款截止日為「推算」值**~~ → ✅ **已精確化（2026-10-02，`CR-036`／`§7.13`）**：於**帳單出帳**時以權威 `paymentDueDate` **校正**。⚠️ **仍為推算值之情形**：該歸戶**長期不出帳**時，迄日將停留於 `DERIVED`（可由 `due_date_source` 辨識並人工處理）。
18. **部分請款之精確回沖未實作**（`§7.12.1`）：8-6 以**請款金額**釋放佔用（POC-8 為全額請款）；部分請款時之精確回沖待後續。 |

---

## 11. 修訂記錄

| 版本 | 說明 |
|---|---|
| **1.20** | **`CR-055` P4-2／P4-3（2026-10-17，`#A-296`）**：**新增 §7.18 預借現金額度**——`V15` 比例欄（`account_limits`／`limit_adjustments`）＋**`V16` 擴充 `limit_holds.hold_type` 值域加入 `CASH_ADVANCE`**（★ 真機攔下之真實缺口：原值域只有 GENERAL／SPECIAL／INSTALLMENT，第一次執行即 `ORA-02290`）；上限＝總額度×ratio、可用＝min(上限−已預借, 有效可用)；`null`（未設定→拒絕）≠`0`（明確禁止）；不享共享池之卡以**卡片自身可用**為有效可用（否則可繞過卡片硬上限）。**新增 §7.19 企業子額度與組織樹**——`V17` `corp_units`＋`limit_holds.corp_unit_id`；上層可用＝總額度−已用−自身佔用−**所有下層佔用之和**；檢核走**上層鏈**並在錯誤訊息指出**不足之層**。★ 揭露：授權端尚未送 `CASH_ADVANCE`；企業額度覆蓋之「申請→覆核」與卡片綁定未接 |
| **1.19** | ⚠️ **本列為回填**（原漏列）：**`CR-055` P4-1（`#A-289`）：新增 §7.17 共享池權利與卡片上限檢核** |
| **1.18** | ⚠️ **本列為回填**（原漏列）：**`CR-054` P1（`#A-288`）：§7.6 佔用事件 payload 補可選 `instrumentId`** |
| 1.17 | **`CR-052` C-3（2026-10-17，`#A-286`）：新增 §7.16 卡片級額度調額寫入**——① `V12__limit_adjustment_subject.sql`：`limit_adjustments` 加 `subject_type`／`subject_id`（既有單回填 `ACCOUNT`、`BEFORE INSERT` trigger 補值——與 `V11` 同一教訓：直接 NOT NULL 會使既有寫入全數失敗）。② 契約 `LimitAdjustmentRequest` 加 `instrumentId`（非 null＝CARD 維度）、`LimitAdjustmentView` 揭露 `subjectType`／`instrumentId`。③ ★ **無 CARD 列時核准後建立**（`before=0`）：此為「客戶提供卡片主額度初始值」之**唯一途徑**，且**不複製**帳戶額度（`CR-053` D-4）。④ ★★ **卡片級不發射 `limit.limit.adjusted.v1`**：該事件 payload 為「帳戶總額度」，若以卡片額度送出將使授權讀模型 `account_limit_caches` 之帳戶總額度被覆蓋（超額放行或誤拒）；卡片額度之授權取值走即時查詢。⑤ ★ **卡片級不支援 `SPECIAL`**（專用額度池為帳戶層，回 `CC-LIM-1003`）。⑥ ★ **實作期修正**：`LimitAdjustment.copy()` 原用帳戶建構子 → 狀態轉換（`submit`／`approve`）會**悄悄把維度退回 ACCOUNT**，導致核准時套到帳戶主額度；已改帶維度並以「待覆核→核准」測試鎖住。⑦ 驗證：**194 測 0 失敗**（新增 8 例真 Oracle）；真機 E2E：卡片調額 `EFFECTIVE`、CARD 列 300,000、ACCOUNT 列**不變**、outbox **未增**、查詢可用 100,000。⚠️ **未達**：卡→帳戶歸屬未經驗證（本服務無對映能力）；C-2 投影分攤未做 |
| 1.16 | **`CR-053` D-1（2026-10-17，`#A-285`）：新增 §7.15 載體（卡）級主額度——額度主體維度化**——① `V11__limit_subject_dimension.sql`：`account_limits` 加 `subject_type`／`subject_id`、既有列**明確回填** `ACCOUNT`、唯一鍵改 `(subject_type, subject_id, currency)`、`BEFORE INSERT` trigger 自動補值（`subject_type='ACCOUNT'` 時 `subject_id := account_id`，其他維度**不推導**）。② 契約 `GET /api/v1/instruments/{instrumentId}/limits`＋`InstrumentLimitView`（**用 instruments 非 cards**：本服務無 `card→instrument` 對映，不得以 cardId 猜 instrumentId）。③ ★ **可用額度為保守下界**：`本卡(永久+有效臨時+彈性) − 帳戶已用 − 本卡可歸屬佔用`（下限 0），並以 `holdAttribution=FULL/PARTIAL` **如實揭露**歷史佔用不可歸屬（`V10` 不得回填）；**不得**據此宣稱精確可用額度。④ ★ **查無即拒絕**（`CC-LIM-1001`），**不以歸戶額度冒充卡片額度**。⑤ ★ **實作期修正兩項真缺陷**：(i) 7 處帳戶層寫入未限定 `subject_type` → 同帳戶多列會使「未帶 currency 時恰 1 列才更新」誤判為多幣別（**繳款恢復額度永久失效**）且污染卡片列；(ii) `subject_id` 直接 NOT NULL 使既有 51 處寫入失敗（實測 51 測試轉紅）→ 改 trigger。⑥ 驗證：**186 測 0 失敗**（新增 11 例真 Oracle）；真機 E2E `300,000 − 200,000 = 100,000`、`PARTIAL` 揭露、查無回 `CC-LIM-1001`。⚠️ **未達（誠實揭露）**：商品參數驅動維度（D-2）與卡片級**調額寫入**（C-3）為後續批次；`HOUSEHOLD`／`PARTY`／`CORP_SUB` 僅值域預留 |
| 1.15 | **`CR-045` D4（2026-10-16，`#A-242`）：§7.14 閉環改採真 socket，並更正 #A-241 之錯誤解釋**——① `ParameterClosedLoopIntegrationTest` 由 `@WebMvcTest`+MockMvc 改為**嵌入式 Tomcat（`server.port=0`）＋ JDK `HttpClient` 真 socket**，六例全通過（② PUT 落新版＋現行版封閉、⑤ businessDate 版控、④ 直改即反映、同交易 Outbox、4 項負樣本 422/404/400/400）。② ★ **根因定位**：#A-241 原將失敗歸因「沙箱 HTTP 攔截代理攔走迴路埠」**不成立**；實測**同埠四路競賽**——`localhost`／`127.0.0.1` → `HttpTimeoutException`，`[::1]`／**hostname** → HTTP 200。真因：**本機嵌入式 Tomcat 僅綁 IPv6 萬用字元，IPv4 迴路連線「掛住」而非「拒絕」**。③ **可複用判準**：本機任何「嵌入式 Tomcat + HttpClient 逾時」→ 先改 **hostname** 或 `[::1]`。④ 品質：`scan_scaffold`／`scan_mockdata`／`scan_faketest` 各 0 命中。⑤ **仍誠實揭露**：③之**跨進程**段（`limit-service` 之 `ProductParameterHttpClient` 打到本服務）尚未驗證——現僅證明本服務自身 web 層閉環。
| 1.14 | **`CR-045` D4（2026-10-16，`#A-241`）：§7.14 驗收 ②／④／⑤ 閉合**——於 `product-service` 新增 `ParameterClosedLoopIntegrationTest`（真 Oracle via Testcontainers ＋ `@WebMvcTest` 真 web 層，`@Import` 生產之 `ParameterService`／`JdbcParameterRepository`／`JdbcOutboxRepository`／`GlobalExceptionHandler`，**零替身**）。★ **動工前查證（`MEMORY-RULES #17`）更正前版註記**：「待 product-service 參數寫入路徑與 `CC_PRODUCT.PARAMETERS` 表俱備」**實為過度保守**——`ParameterController` 之 `PUT`、`ParameterService` 之版本鏈與生效日重疊檢查（`CC-PRD-1002`）、`V1__baseline.sql` 之 `parameters` 表**三者本地早已具備**，②／④／⑤ **並非被外部阻塞**，僅缺跨服務真機實驗。★ **六例全數通過**：② PUT 落新版＋現行版封閉於 `newFrom−1`、⑤ businessDate 取昨日／今日得舊值／新值、④ 直改 `CC_PRODUCT.PARAMETERS` 後再取即反映（非快取）、同交易 Outbox 事件 2 筆、4 項負樣本（422 `CC-PRD-1002`／404 `CC-PLT-4040`／空 body 400／缺 header 400）。⚠️ **根因更正（同日續，原解釋有誤）**：當時將失敗歸因「沙箱 HTTP 攔截代理攔走迴路埠」**不成立**；實測根因為 **Tomcat 僅綁 IPv6 萬用字元、IPv4 迴路 `127.0.0.1`（`localhost` 優先解析 IPv4）之連線會掛住而非拒絕**，改用 hostname 或 `[::1]` 即 HTTP 200 → 已改採 MockMvc 為保守選擇（根因未明時），**真 socket 版本並無技術阻礙**。**③之真 HTTP 段**（`limit-service` → `product-service`）仍未閉合，根因既明，列為後續交付單元。★ 品質：`scan_scaffold`／`scan_mockdata`／`scan_faketest` **各 0 命中** |
| 1.12 | **`CR-045` D4（2026-10-15，`#A-238`）：新增 §7.14 商品參數消費閉環（首案）**——spec-first，不直接改程式。★ **緣由**：`CR-045` P2 指出本服務未消費商品參數（`product-service` 之 `GET /api/v1/parameters/{ns}/{key}` 生產端已備、無消費者）。★ **首案收斂**：不選 `limit.model`/`shared_scope`（其 account/card/family/corporate 分層為 OPEN 待裁定 A1），改選 **已在 §7.2 完整定義**之覆核門檻，由商品參數 `limit.approval-threshold-minor` 驅動 → 證明「改權威參數值 → 額度行為改變」閉環。★ 新增 `ProductParameterPort`（同步取參、依 `businessDate` 取版本；快取僅於量測後引入）。★ **真機驗收範式**沿用 §7.7／§7.9.7：改 `CC_PRODUCT.PARAMETERS` 值 → 同一申請之覆核路徑改變 → 繞過服務改 DB 即反映（證非快取）。★ **OPEN**：A1 多維度分層、A2 同步 vs 事件消費、A3 參數寫入 RBAC（D6）、A4 取參失敗降級。★ 不恢復 stale endpoint（D2）、不臆測活動政策（D3） |
| 1.11 | **`CR-036`（2026-10-02，`#A-157`）：新增 §7.13 8-6 迄日之精確化**——診斷推算式之誤差來源（最長高估近一個帳單週期）與「帳單日在 `CC_PARTY`、**跨 schema 不可讀**」之限制。★★ **關鍵洞察**：精確值**在原理上**只有帳單出帳後才存在 → 裁定採**兩階段**（① 請款時調高並給推算值 `DERIVED`；② **帳單出帳時**以**權威 `paymentDueDate`** 校正為 `paymentDueDate + 7`，標記 `STATEMENT`）。★ **校正歸屬條件**：僅 `raised_on ≤ 出帳日`（出帳日取信封之 `occurredAt`，**不**由 `paymentDueDate − 25` 反推）。★ `temp_end_date` 同步為**所有 OPEN 紀錄之最晚迄日**。★ **契約無需變更**（payload 早已含 `accountId`／`paymentDueDate`）；架構變更僅 `SAD-004 §5.2` 之消費者 `notification` → `notification` ＋ `limit`。**真機 3 項**（含**負樣本**：出帳日早於調高日 → 不校正）。**測試**：69 測 0 失敗 |
| 1.10 | **`CR-035 §2.4`（2026-10-02，`#A-156`）：新增 §7.12 臨時額度串動（POC-8 之 8-6／8-7）**——`BR-04-02`「請款後調高至繳款截止日＋7 日；繳款後調降」。**新增**：`V9__special_limit_temp_adjustments.sql`；`SpecialLimitTempAdjustment` ＋ 埠 ＋ JDBC；`SpecialLimitTempAdjustmentService`（8-6 調高／8-7 FIFO 回收）；`PostingTransactionPostedListener`（**本服務原已列為該事件之消費者，惟從未實作**）；`LimitRepository#raiseTemporary`（★ **相對增量**，非 `applyAdjustedAmount` 之絕對值）／`lowerTemporary`／`findSpecialHoldIdByAuthorization`；`PaymentPostedProjectionService` 併入 8-7（**窄埠** `TempAdjustmentRecoveryPort`）。★ **生產端**：`posting-service` 之 `posted.v1` payload 補**可選** `authorizationId`（來源欄位早已存在，**不升版**）。★★ **兩項關鍵設計**：**(i)** 8-7 **必須逐筆扣回**，**不得**直接將 `temporary_amount` 歸零（該欄可能同時承載**人工核准之臨時額度**）；**(ii)** 繳款截止日以「入帳日＋帳單週期(25)＋7」**推算**並標記 `DERIVED`——★ **更正 `CR-035 §2.4` 原設計**（原載讀 `CC_STMT` 為**跨 schema**、**不可行**）。**真機 6 項**（真實 Kafka ＋ Oracle）。**測試**：limit **64 測**／posting **35 測**，皆 0 失敗 |
| 1.9 | **`CR-035 §2.3`（2026-10-02，`#A-155`）：新增 §7.11 專用額度之「可設定上限」政策（POC-8 之 8-2）**——`BR-04-03`「依角色 ＋ 客層參數化」。**新增**：`V8__special_limit_policies.sql`；`SpecialLimitPolicy` ＋ `SpecialLimitPolicyRepository` ＋ `JdbcSpecialLimitPolicyRepository`；★ **`CallerIdentityPort` ＋ `SecurityContextCallerIdentity`**（**角色自已驗證之權杖**：服務權杖取 `azp`、使用者權杖取 realm roles）；`apply()` 之政策檢核（**fail-closed**）；`GET`／`PUT` 兩個維護端點。★★ **折衷之處理**：**角色之折衷移除**（不接受請求帶入，**防提權**）；⚠️ **客層仍由請求帶入**（四項證據，`§7.11.3`）。★★ **真機揭露之陷阱**：M2M 權杖之 realm roles 實為 `default-roles-cardcore`／`offline_access`／`uma_authorization`（**皆無區辨力**）→ 若先取 realm roles，角色會被解析為 **`offline_access`**（**荒謬**）→ 改以 **`azp` 以 `cc-` 開頭**（**與 `ServiceCallerAclManager` 同一判準**）**優先**判定服務權杖。**真機驗證 6 項**（`§7.11.6`）。**測試**：55 測 0 失敗（新增 14） |
| 1.8 | **`CR-035 §2.1／§2.2`（2026-10-02，`#A-154`）：新增 §7.10 專用額度池之佔用與適用性**——POC-8 之 **8-3**（走專用池、**不佔卡人額度**）、**8-4**（無適用則回歸卡人額度）、**8-5**（兩池皆不足 → 拒絕）。**新增**：`special_limits.scope_ref` ＋ `limit_adjustments.scope_ref`（**MCC 白名單**；`NULL` ＝ 不限定）＋ `limit_holds.special_limit_id`（`V7__special_limit_pool.sql`）；`SpecialLimit#coversMcc`／`hasExplicitScope`／`availableMinor`；`SpecialLimitRepository#findApplicable`（**決定性選擇順序**）／`findById`／`increaseUsed`（**條件更新**，防併發超額佔用）／`decreaseUsed`；`LimitApplicationService#hold` 之 **SPECIAL 池路徑**與 `release` 之**對稱回沖**；新端點 **`GET /accounts/{id}/special-limits/applicable`**（★ **`applicable=false` 回 200**——「無適用」與「服務故障」必須可區分）。★ **`used_amount` 之語意為「佔用中」**（`§7.10.2`）。**真機驗證 11 項**（`§7.10.4`）：含 **8-3 之 `availableLimit` 不因專用佔用而減少**、**`account_limits` 全程未變**、**8-5 之 DECLINED 51**、**釋放之對稱回沖**。**測試**：`limit-service` 41 測 0 失敗（新增 `SpecialLimitPoolTest` 13） |
| 1.7 | **`CR-014 v1.1`（2026-10-02，`#A-153`）：新增 §7.9 專用額度（POC-8）之寫入與查詢**——接線**既有** `SPECIAL_LIMITS`（**非建表**；該表由 `V1__baseline.sql` ← `SDD-001 §3.4` 早已存在，`CR-014 v1.0` 之「新表」誤述已更正）。**新增**：`SpecialLimit` ＋ `SpecialLimitRepository` ＋ `JdbcSpecialLimitRepository`；`V6__special_limits_currency_and_index.sql`（補 `currency`、索引、**唯一索引 `uk_special_limits_active`**）；`apply()` 之 `SPECIAL` 基準額度改取專用額度池、`applyToMaster()` 之 SPECIAL 分支（`upsertActive`）、`revoke()`（`cancelActive`）、查詢端點**改讀 `special_limits`**、到期排程併入 `§7.4`、`purpose` 值域檢核（`CC-LIM-1003`）。★ **契約破壞性變更**（`§7.9.5`）：`SpecialLimitItem` 由「調整視角」改為「定義視角」（消費端為零，不另立 `v2`）。**併補** §7.1 之「專用額度不計入可用額度」、§10 #2 解除與 #12～#15 之誠實揭露、§9 之 UT-LIM-13～18。**真機驗證**（§7.9.7）：13 項全通過（含「繞過服務改 DB → 查詢立即反映」之**非快取**證明、「不佔卡人額度」之 `account_limits` 全程未變、到期排程約 40 秒生效）。★ **併同修正真機揭露之契約缺陷**：`POST /limit-adjustments` 實作欄位 `targetAmount` ↔ 契約 `amount` 不符（依契約送件一律 400）、契約漏列必填之 `applicantId` —— 已一併修正（實作改名 `amount`；契約補 `applicantId`／`currency`） |
| 1.6 | 2026-10-01 | WorkBuddy AI | **`CR-030 §2 #9`（`#A-142`）：新增 §7.8 繳款「重新佔用」事件消費**——訂閱 `payment.payment.unmatched.v1`／`.reversed.v1`，**重新佔用額度**（`used_amount += amount`）；★ **真缺陷之修補**（`SCS-008 §7`／`§8.2` 之「撤銷額度恢復」原無任何消費者 → 可用額度**永久高估**）。★ 併載「本消費**不是**重做同步已做之寫入」之界限（`CR-030 §2 #16`）。★ 因 payload 帶 `currency` → 可**精確定位**額度主檔 |
| 1.5 | **`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 B（2026-10-01，`#A-128`）**——**§7.6 payload 補 `currency` 為可選欄位**：原 payload 未帶，惟 `CC_AUTH.LIMIT_HOLDS.currency` 為 **`NOT NULL`** → 消費端（`authorization-service` 之本地投影）**無法寫入**。limit-service **本來就有**該值（`hold()` 之入參），依 `SAD-004 §5.3` **不升版**。已列 `CR-027 §5 #14` 待裁定。**併補** §10 之說明 |
| 1.4 | **`CR-028` hold 事件群四項矛盾之化解（2026-10-01）**——①**§7.6**：`authorizationId` 由「**刻意不納入**」改為**可選欄位**（依 `SAD-004 §5.3` **不升版**；證據：`CC_LIM.LIMIT_HOLDS` 與 `CC_AUTH.LIMIT_HOLDS` **皆有 `AUTHORIZATION_ID`**）；②**§7.6 緣由**：移除「`authorization.hold.*` 語意矛盾待裁定」之註記，改為**已化解**（該二事件**已廢除**）；③**§10 #8／#9** 由「待裁定」改為**已化解**。**本輪不動程式**（實作屬批次 B） |
| 1.3 | **`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 A-1：§7.7 繳款入帳事件消費（2026-10-01，`#A-124`）**——新增 **`payment.payment.posted.v1` → `account_limits.used_amount`（恢復額度）** 之消費端規格（`SAD-002 §3.2` 明列本服務訂閱、`SCS-008 §8` 亦載「經 Transactional Outbox 事件非同步通知」，惟**消費端從未實作**）。**實作**：`V5__event_consumption.sql`（`consumed_events`）＋ `PaymentPostedProjectionPort`／`Service`／`JdbcPaymentPostedProjectionRepository`／`PaymentPostedListener`。★ **恢復金額＝`amount`**（`GREATEST(0, …)`）；★ **亂序判準置於 `consumed_events`**（**不**在權威主檔增設技術欄位）；★ **查無主檔不建列、不寫 `consumed_events`**（維持「已記＝已套用」不變式）。**同時解除 §10 #3 之部分限制**（`used_amount` 現會因繳款而下降）。**真機**：`used_amount` **500,000 → 300,000**；冪等／亂序／溢繳下限／查無主檔皆通過。**併補** §10 #10／#11（`UNMATCHED`／`REVERSED` 事件未實作；`posted.v1` 缺 `currency`） |
| 1.2 | **`GOV-006` 4.8 W-2 推廣・事件 #4／#5：§7.6 佔用事件輸出（2026-10-01，`#A-122`）**——新增 **`limit.hold.created.v1`／`limit.hold.released.v1`** 之生產端規格（`SAD-002 §3.2` 明列本服務發布、`SDD-006 §4.2` 已定義 payload，**卻從未有人發射**）。**權威歸屬無爭議**（`limit_holds` 之權威在本服務）。**實作**：`LimitEventFactory` 新增兩個信封產生器 ＋ `LimitApplicationService.hold()`／`release()` **同交易**寫 outbox；併補 `LimitRepository.findHoldAccountId`（釋放入參不含帳戶，事件需回查）。**併補** §10 #8／#9 之誠實揭露（消費端未實作、payload 缺 `authorizationId`） |
| 1.1 | **`GOV-006` 4.8 W-2 推廣・事件 #2：§7.5 額度事件輸出（2026-10-01）**——新增 **`limit.limit.adjusted.v1`** 之生產端規格（`SAD-004 §5.2` 載明卻從未實作）。**主題** `cc.limit.limit.adjusted`；**同交易寫 Outbox** ＋ relay 發布；分區鍵 `accountId`。**併補** §10 之五項誠實揭露（撤銷／到期未發射、序號簡化、`eventId` 非 ULID 等）。 |
| 1.0 | 初版：回溯補建，與既有程式一致 |
