# SCS-001　授權服務（authorization-service）程式設計規格

| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-001 |
| 版本 | **1.17**（`CR-054` P2／P3：§7.15 佔用事件消費補 `instrumentId` 落地；**新增 §7.18 卡片級額度投影查詢**——`V14` 投影加 `instrument_id`、`GET /limit-projections/…`、`ProjectionLagMonitor` 落後告警。前版 **1.16+**（`CR-048`：`AuthorizeRequest.payPath` 結構化支付路徑欄位；`V13__authorization_pay_path.sql`）**（`CR-043 §2.1／§2.2`：**reversals／overrides 之回應型別修正**——原契約**誤宣告 `AuthorizeResponse`**（含 `decision`／`checkResults`，對沖正／核決**語意不合理**），改為 **`ReversalResult`**（`authNo`／`type`／`amountMinor`／`holdReleased`／`reversedAt`）與 **`OverrideResult`**（`authNo`／`decision`／`force`／`holdReleased`／`overriddenAt`）；§6 API 表更新＋§7.6／§7.8.2 補記。★ 契約重生 **0 錯誤**；`verify_contract_dto` **不符 0 處**）。前版 **1.15**（`CR-043 §2.3`：**新增 §7.17 授權請求之特店名稱／輸入模式／EMV 資料之接收**——契約 `AuthorizeRequest` 宣告之 `merchantName`／`entryMode`／`emvData` 三欄，實作 DTO 無此三欄 → **靜默忽略**；BEAM PMO 2026-10-15 裁定「**請補實作接收**」（`CR-043 §4` 之 **(B)** 案）。★ `merchant_name`／`entry_mode` **早已在 `V1__baseline.sql`** 卻**從未被綁定**（恆 NULL）——本次接線；`emv_data` 由 **`V10__authorization_entry_emv.sql`** 新增（Oracle 23ai `JSON`）。★ **回應不擴充**（契約未宣告）＝接收＝**落庫可稽核**。併記 `CR-044 ③`（§7.6／§7.15 之**權威鏡射**：部分沖正維持 `HELD`）。前版 **1.14**（`CR-035 §2.2`：**新增 §7.16 額度池選擇**——POC-8 之 **8-3／8-4／8-5**；`SpecialLimitPort` ＋ `StandInSpecialLimitPort`（安全降級）＋ 三段式池選擇；★ `BR-04-01` 為**結構性成立**）。前版 **1.13**（`CR-030` 架構裁定：#8 `REFERRAL` 發射（真缺陷）＋ `decision` 欄位；#7 `DECLINED` 刻意不發射）。前版 **1.12**（`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 B：**新增 §7.15 佔用事件消費**（`limit.hold.created.v1`／`.released` → 本地 `limit_holds` 投影）——補 `V2__read_model.sql` 明文載明卻**從未有人餵養**之**第三個**讀模型缺口。★ **以 `authorizationId` 去重**避免 stand-in 後送造成「同一筆佔用兩列 → 重複扣減」；★ 亂序判準置於 `consumed_events`（不動業務表）；★ **併修正 `JdbcLimitPort.availableLimit` 之 status 過濾缺陷**（原僅計 `HELD`，漏計 `PARTIALLY_RELEASED` → 低估佔用、高估可用額度）。真機驗證（真實 Kafka ＋ Oracle，**含 stand-in 去重之負樣本**）。**併補** §10 #9～#11。前版 **1.11**（`GOV-006` 4.8 W-2 推廣・事件 #3：**新增 §7.14 授權核准事件輸出**（`authorization.authorization.approved.v1`）——`SDD-003` 時序圖與 `SDD-006 §4.2` **均已載明、卻從未有人發射**；**生產端**（同交易寫 Outbox ＋ relay），**消費端待下一片**。**併補** §10 #6／#7 誠實揭露。前版 **1.10**（`GOV-006` 4.8 W-2 推廣・事件 #2：**新增 §7.13 額度事件消費**（`limit.limit.adjusted.v1` → `account_limit_caches`）——補 `V2__read_model.sql` 明文載明卻**從未有人餵養**之第二個讀模型缺口；含**冪等**（共用 `consumed_events`）＋**亂序防護**（`account_limit_caches.last_sequence_no`）；**只覆寫 `total_amount`、`used_amount` 原值保留**。⚠️ 併處理**命名落差**：`V2` 註解之 `limit.limit.changed.v1` vs `SAD-004 §5.2`（權威）之 `limit.limit.adjusted.v1`——**依 `SAD-004 §5.2`**。真機驗證（真實 Kafka ＋ Oracle）通過。前版 **1.9**（`GOV-006` 4.8 W-2 第一切片：**新增 §7.12 卡片狀態事件消費**（`card.card.status-changed.v1` → `card_status_caches`）——補 `V2__read_model.sql` 明文載明卻**從未有人餵養**之讀模型缺口；含**冪等**（`consumed_events`，與 upsert 同交易）＋**亂序防護**（`last_sequence_no`）；真機驗證（真實 Kafka ＋ Oracle）通過。前版 **1.8**（桶 C 解鎖（`#A-114`）：新增 **§7.11 交易狀態機檢視**（`GET /api/v1/authorizations/{authNo}/state`）——解鎖 `SDD-007 §5.1.2.3` 桶 C「交易狀態機與冪等」；狀態機視角（當前 `decision` ＋ `limit_holds` 佔用 ＋ 由 `created_at`／`override_at`／`reversed_at` 推導之轉移歷程）；**冪等（Redis）如實揭露不重現**。契約重生 0 錯誤。前版 **1.7**（`CR-026` 新增統計端點 `GET /api/v1/authorizations/stats`（2026-10-01）**：新增 **§7.10** 授權統計（供 `/dashboard` 業務 KPI）；`pendingUpload`＝`standin_flag=1 AND standin_upload_at IS NULL`（依 `V4__standin_upload.sql` 權威註解）；`today`＝當日授權數（`decision` 不篩選）。契約重生 0 錯誤。前版 **1.6**（**`CR-025` 稽核軌跡查詢端點（2026-09-29）**：新增 **§7.9 `GET /authorizations/audits`**——解鎖 `SDD-007 §5.1.2.3` 桶 C「稽核軌跡」（原記「**表存在但無查詢端點**」）。**設計三要點**：**統一時間軸**（合併 `auth_overrides`／`auth_reversals`）／**`action` 值域正規化**／**`actor` 對沖正如實回 `null`**（該表無操作者欄位——**不臆造**）。**append-only 唯讀**。契約重生 **79 端點、0 錯誤**）。前版 **1.5**（**`CR-023` 後送作業台之端點補齊（2026-09-24）**：**§7.7 之「職責」表補明端點對應**——原表僅列三項職責而**未對應端點**，實作**僅有單筆**之 `POST …/{authNo}/advices`，致「**查詢待補送佇列**」與「**批次手動上送**」**無端點可達**（`SDD-007 §5.4.23` 之 `/auth/stip` 頁**無法完整實作**）。新增 `GET /authorizations/standin?status=PENDING\|UPLOADED\|ALL` 與 `POST /authorizations/standin/advices`；契約重生 **77 端點、驗證 0 錯誤**；**併明訂**清單**不得以決策碼篩選**（`§7.7.1`））。前版 **1.4**（**`CR-022` 真機驗證後補記（2026-09-24）**：新增 **§7.7.1 `standin_flag` 之判定依據**——須依「**佔用方式**」而非決策碼；**併修實作**。實測發現 `§7.8` 新增 `REFERRAL` 後「`decision=STANDIN`」與「`standIn=true`」**不再等價** → 轉人工件在額度服務不可用時**漏標 `standin_flag`** → **永不後送**（超額風險）。**§9 補 24 例單元測試＋7 項真機實測**）。前版 **1.3**（**`CR-022` 裁定後之實作規格**：**§7.7 改寫為 (A) 後送授權通知**（BEAM PMO 2026-09-24 裁定；含與排程上送之分工、冪等守門、契約修訂定案）；**新增 §7.8 人工授權／強制授權**——**RFP `FR 1.1`（授權／發卡授權）第 4 項明文要求**「支援人工授權、強制授權、人工取消授權」，**廠商回覆欄已填「滿足」**；其中人工取消＝沖正（§7.6）已完成，本節補人工／強制授權；含**參數化轉人工門檻**（`CheckCode.MANUAL_REVIEW`）、狀態守門、**僅限對 `REFERRAL` 核決**（不得對 `DECLINED` 強制核准，以免繞過風控）。前版 **1.2**（`CR-021` 真機驗證後補記 **§7.6 雙軌釋放**——`StandInLimitPort.release` 與 `hold` 對稱（遠端優先、失敗降級本地並 WARN、遠端成功後 best-effort 清本地同列）；**FULL 沖正已真機驗證**（`AUTH000035`：hold 25000 → `RELEASED`、`AUTH_REVERSALS` 落列、`reversed_at` 標記、重複沖正 `CC-AUTH-3002`）。⚠️ **併發現既有缺陷**：`LimitServiceClient`（Feign）對 limit-service 之呼叫回 **401**（未接 M2M outbound token）→ **授權一律降級 Stand-in**——此為 `CR-010/011` 範疇之既有缺口，已登 `GOV-006 §4 #21`）。前版 **1.1**（**`CR-021` 補實作之規格先行**：§6 補 `check-results`／`reversals` 兩端點（**契約此前宣告而未實作**）＋ `advices` 標記「語意待裁定、本期未實作」；新增 **§7.5 檢核明細查詢**（查落庫、不重算；併修正契約之**陣列性不符**）、**§7.6 沖正**（`SDD-005 §3` 轉換 5；新增 `LimitPort.release`；同交易寫 `auth_reversals`＋`reversed_at`；**不修改原決策欄**）、**§7.7 advices 兩種語意之裁定建議**。併載明路徑鍵 `{authNo}`（業務代碼）之一致性。前版 **1.0** |
| 狀態 | 已核定（回溯補建，與現有程式一致） |
| 對應需求 | 見 §1 |
| 服務埠號 | 8081 |
| 套件根 | `com.beam.cardcore.auth` |
| 權威 schema | `CC_AUTH` |

---

## 1. 對應需求

| FR 編號 | 需求名稱 | 來源 |
|---|---|---|
| `AUT-1.1` | 授權處理（含即時性） | 建議書授權章節 |
| `AUT-1.2` | 授權分流（Stand-in／代授權） | 建議書授權章節 |
| `AUT-1.3` | 國際組織 compliance（檢核） | 建議書授權章節 |
| `SYS-2.1` | 讀寫分離（CQRS） | 橫切能力 |

---

## 2. 服務職責與邊界

**承作**：
- 受理授權請求、執行檢核、產生決策
- 額度佔用之**協調**（實際佔用由 `limit-service` 執行）
- 卡片狀態檢核之**協調**（權威狀態由 `card-service` 提供）
- 降級（Stand-in）時以本地讀模型代判並標記 `standin_flag`

**不做（屬他服務）**：
- 額度主檔維護 → `limit-service`
- 卡片主檔與生命週期 → `card-service`
- 入帳／請款 → `posting-service`（未開發）

---

## 3. 領域模型

### 3.1 聚合與值物件

| 型別 | 職責 |
|---|---|
| `Authorization` | 授權聚合（不可變）。含決策、檢核結果、佔用金額、`standIn` 標記 |
| `CheckCode` | 13 大項檢核列舉；`isImplemented()` 標示是否有資料來源 |
| `CheckResult` | 單項檢核結果（`passed`／`respCode`／`message`） |
| `HoldResult` | 佔用結果（`holdId` + `standIn` 標記） |
| `CardSnapshot` | 卡片快照（可否交易以 **`blockCode`** 判定，見 §7.3 |

### 3.2 授權決策狀態機

| 來源 | 事件 | 目標 |
|---|---|---|
| — | 核准 | `APPROVED` |
| — | 拒絕 | `DECLINED` |
| — | 轉人工 | `REFERRAL` |
| — | 額度服務不可用而本地代佔 | **`STANDIN`** |

> 依契約 `AuthorizeResponse.decision` 列舉。`isApproved()` 對 `APPROVED`／`STANDIN` 回傳 true。

**關鍵設計**：降級佔用時決策為 **`STANDIN` 而非 `APPROVED`**——使下游可識別「此筆未經額度服務確認，須事後上送」。

---

## 4. 程式結構

| 套件／類別 | 職責 | 對應需求 |
|---|---|---|
| `domain/model/Authorization` | 授權聚合 | AUT-1.1 |
| `domain/model/CheckCode` | 檢核項目列舉 | AUT-1.3 |
| `domain/model/CardSnapshot` | 卡片快照 | AUT-1.1 |
| `domain/port/CardPort` | 卡片查詢埠 | AUT-1.2 |
| `domain/port/LimitPort` | 額度埠 | AUT-1.2 |
| `domain/port/AuthorizationRepository` | 授權持久化埠 | AUT-1.1 |
| `application/AuthorizeUseCase` | 授權編排（檢核→佔用→裁定→持久化） | AUT-1.1 |
| `application/StandInUploadService` | Stand-in 佔用事後上送 | AUT-1.2 |
| `infrastructure/jdbc/JdbcAuthorizationRepository` | JDBC 實作 | AUT-1.1 |
| `infrastructure/jdbc/JdbcCardPort` | 本地讀模型（降級用） | AUT-1.2 |
| `infrastructure/jdbc/JdbcLimitPort` | 本地代佔（降級用） | AUT-1.2 |
| `infrastructure/client/CardServiceClient` | Feign → card-service | AUT-1.2 |
| `infrastructure/client/LimitServiceClient` | Feign → limit-service | AUT-1.2 |
| `infrastructure/card/RemoteCardPort` | 遠端卡片查詢 | AUT-1.2 |
| `infrastructure/card/StandInCardPort` | 卡片降級裝飾器 | AUT-1.2 |
| `infrastructure/limit/RemoteLimitPort` | 遠端額度 | AUT-1.2 |
| `infrastructure/limit/StandInLimitPort` | 額度降級裝飾器 | AUT-1.2 |
| `infrastructure/limit/RemoteLimitUploadAdapter` | 上送（不降級） | AUT-1.2 |
| `infrastructure/scheduler/StandInUploadScheduler` | 上送排程 | AUT-1.2 |
| `interfaces/rest/AuthorizationController` | REST 端點 | AUT-1.1 |

---

## 5. 資料模型

| 表 | 關鍵欄位 | 實測約束 |
|---|---|---|
| `authorizations` | `auth_no`(UNIQUE)、`decision`、`resp_code`、`hold_amount`、`standin_flag`、`hold_id`、`standin_upload_at`、`merchant_name`、`entry_mode`、`emv_data` | `decision IN ('APPROVED','DECLINED','REFERRAL','STANDIN')`；`standin_flag` NOT NULL DEFAULT 0；`hold_id`／`standin_upload_at` 由 V3／V4 新增；`merchant_name`／`entry_mode` 見 `V1__baseline.sql`（惟**至 `CR-043 §2.3` 方接線落庫**），`emv_data` 由 **V10** 新增（`JSON` 型別）——見 §7.17。

| `auth_check_results` | `check_code`、`check_order`、`result`、`reason_code` | `result IN ('PASS','FAIL','SKIP')` ← 實測。**曾誤寫 PASSED/FAILED 導致 `ORA-02290`**。

| `card_status_caches`（本地讀模型） | `card_no_hash`、`block_code`、`status`、`expiry_date` | 無 enum 約束（自建表）；`block_code` 值須與 `CC_CARD.CARDS.block_code` 一致

---

## 6. API 設計

| 端點 | 請求 | 回應 | 錯誤碼 |
|---|---|---|---|
| `POST /api/v1/authorizations` | `AuthorizeRequest` | `AuthorizeResponse` | `CC-AUTH-1001`～1006、`CC-LIM-1001` |

| `GET /api/v1/authorizations?limit` | — | `AuthorizationSummary[]` | — |
| `GET /api/v1/authorizations/{authNo}` | — | `AuthorizeResponse`（含檢核明細） |
| `GET /api/v1/authorizations/{authNo}/check-results` | — | `AuthCheckResult[]`（逐項檢核） | `CC-AUTH-4040`（查無） |
| `POST /api/v1/authorizations/{authNo}/reversals` | `ReversalRequest` | **`ReversalResult`**（★ `CR-043 §2.1`：原誤宣告 `AuthorizeResponse`） | `CC-AUTH-3002`（已沖正）、`CC-AUTH-4040`、`CC-LIM-1001` |
| `POST /api/v1/authorizations/{authNo}/overrides` | `OverrideRequest` | **`OverrideResult`**（★ `CR-043 §2.2`：原誤宣告 `AuthorizeResponse`） | `CC-AUTH-3001`（非轉人工件／已核決）、`CC-AUTH-3002`（已核決）、`CC-AUTH-4040`（查無） |
| `POST /api/v1/authorizations/{authNo}/advices` | （**待裁定**，見 §7.7） | — | — |
| `GET /api/v1/authorizations/stats` | — | `AuthorizationStats`（total／today／byDecision／pendingUpload）★ `CR-043 §3.1` #2：原 `…View` 更名 | — |

> ⚠️ **路徑鍵之說明**：契約以 `{authId}` 命名、實作**沿用既有明細端點之 `{authNo}`**
> （`AUTH000123` 業務代碼）——與 `GET /authorizations/{authNo}` 一致，避免同服務兩種鍵。
> **已於 `gen_openapi.py` 補 `description` 明載「授權編號 authNo（業務代碼）」**。
> 契約登錄：三端點已由 `CR-021` 補實作（此前契約宣告而未實作，消費端呼叫為 404）。

---

## 7. 關鍵邏輯

### 7.1 檢核順序

`AMOUNT → CURRENCY → CARD_STATUS → CARD_EXPIRY → MCC → LIMIT`

任一失敗即 `DECLINED`，回應碼取**第一個失敗項**之 `respCode`。

### 7.2 交易邊界

授權紀錄與檢核明細**同一交易**（`AuthorizeUseCase` 為 `@Transactional`）。

### 7.3 可否交易之判定（重要修正）

**以 `blockCode` 判定，不是 `status`**。

- 依 `SDD-005 §2.3`：僅 `blockCode == ACTIVE` 可交易；`LOCKED`/`LOST` 須即時拒絕。

- **曾發生的洞**：原以 `status` 判定，但卡片生命週期轉換改的是 `block_code` → 卡片已 `CLOSED` 時 `status` 仍 `ACTIVE` → **授權照樣核准**。已修正並端到端驗證。

### 7.4 Stand-in 降級

| 依賴 | 降級行為 |
|---|---|
| `card-service` 失敗 → `StandInCardPort` 讀本地 `card_status_caches`，WARN 記錄（鎖卡可能未即時生效） |

| `limit-service` 失敗 → `StandInLimitPort` 本地代佔，`HoldResult.standIn=true` → 決策 `STANDIN`，`standin_flag=1` |

**上送不降級**（`RemoteLimitUploadAdapter` 失敗即拋，保留 `standin_upload_at IS NULL` 待重試。

> ⚠️ **排查教訓**：降級會掩蓋整合失敗。曾因欄位改名（`active`→`transactable`）導致 Feign `DecodeException` → 每次都降級，系統看似正常但其實一直用快取。
> **排查請先看降級 WARN 次數。**

---

### 7.5 檢核明細查詢（`GET /authorizations/{authNo}/check-results`）

- 資料來源為 **`CC_AUTH.auth_check_results`**（授權寫入時**同一交易**落庫，
  見 §7.2）——**查詢不重算**：重算即「兩套真相」（檢核當下之輸入事後可能已變）。
- 回應欄位對應契約 `AuthCheckResult`：`checkCode`＝`CheckCode.name()`、
  `checkOrder`＝檢核順序（§7.1）、`result`＝`PASS`／`FAIL`（`SKIP` 保留予未實作項目）、
  `reasonCode`＝`respCode`。
- ⚠️ **契約修正（`CR-021`）**：原宣告**單一物件**、描述卻載「回傳 13 項檢核明細」
  ——**陣列性不符**（與 `CR-016 §8` 同型），本服務實作前先修契約為 `AuthCheckResult[]`。
- 查無授權 → `CC-AUTH-4040`（404）。

### 7.6 沖正（`POST /authorizations/{authNo}/reversals`）

- **依據**：`SDD-005 §3` 佔用狀態機轉換 5——`HELD --取消／沖正--> RELEASED`
  （全額釋放）。
  ★ `CR-044 ③`（BEAM PMO 2026-10-15 裁定「**以 `limit-service` 為權威、讀模型鏡射**」）：
  部分沖正（`PARTIAL`）對應**部分釋放**，而權威端之釋放 SQL 為「**僅全額才轉 `RELEASED`**」
  → 部分釋放**只累計 `released_amount`、不改變 `status`**（維持 `HELD`）。
  ★ 讀模型（`CC_AUTH.LIMIT_HOLDS`）**必須鏡射之**，不得改寫 `PARTIALLY_RELEASED`
  （該值於本期權威**不可達**，僅保留於 DDL／契約值域，見 `SDD-005 §3.0.1`）。
- **流程**：①查授權（查無 → `CC-AUTH-4040`；**已沖正 → `CC-AUTH-3002`（409，終態不得再沖正）**）
  ②以 `authorization.holdId` 呼叫 `LimitPort.release(holdId, amount)`（**FULL＝原佔用金額**、
  `PARTIAL`＝請求之 `amount`，須 ≤ 未釋放餘額）③寫入 `auth_reversals`（稽核留痕）
  ④標記 `authorizations.reversed_at`（**同一交易**——「額度已釋放但沖正紀錄寫入失敗」
  為難以收拾之半完成狀態）。
- ★ **回應型別（`CR-043 §2.1`）**：回 **`ReversalResult`**（`authNo`／`type`／`amountMinor`／
  `holdReleased`／`reversedAt`）——★ 原契約**誤宣告 `AuthorizeResponse`**（含 `decision`／`checkResults`，
  對沖正**語意不合理**；疑為三端點同範本之複製貼上）；現已改為 `ReversalResult`（實作語意正確）。
- ★ **雙軌釋放（與 `hold` 對稱——「怎麼佔的就怎麼放」）**：`StandInLimitPort.release`
  先試**遠端**（佔用可能已上送、或本來就是遠端佔用）；遠端失敗（即 Stand-in 情境：
  額度服務不可用）→ 改由**本地讀模型**（`CC_AUTH.LIMIT_HOLDS`）釋放並 **WARN 記錄**
  （與 hold 降級同規則：容許但必須可觀測）。遠端成功後以 best-effort 釋放本地同列
  （避免上送前後之雙軌狀態漂移）。本地釋放語意與 limit-service 一致：
  查無／終態／超釋皆拒絕（`CC-LIM-1001`）。
- **冪等由佔用狀態機保證**：`RELEASED` 為終態、重複釋放由額度服務拒絕（`CC-LIM-1001`），
  加上本服務之 `reversed_at` 檢查，**雙保險**。
- ⚠️ **新增 `LimitPort.release(holdId, amount)`**（原埠僅有 `hold`／`availableLimit`）——
  由 `RemoteLimitPort` 轉呼額度服務 `POST /limit-holds/{holdId}/releases`。
- **回應**：200 `AuthorizeResponse`（沖正後之授權視圖，含最新檢核明細）。
- **不修改授權之決策欄**（decision/respCode 為**當時之事實**）——沖正係**新增反向紀錄**
  （`SDD-005 §4` 轉換 4「產生反向交易，不修改原紀錄」之同一原則），僅以
  `reversed_at` 標記不可再沖正。

### 7.7 後送授權通知（`POST /authorizations/{authNo}/advices`）——**(A) 後送，BEAM PMO 2026-09-24 裁定**

> **裁定**：採 **(A) 後送（Advice）**。理由：與原型模組 9 之 L2
> 「Stand-in 獨立授權（開關門檻／超時代行／**待補送佇列／補送結果**）」高度吻合、
> **既有 `StandInUploadService` 可重用**、且沖正（§7.6）已完成 → 可讓
> **Stand-in 生命週期閉環**（佔用 → 上送 → 沖正 → 後送查詢）。
> **依據**：`CR-022` 三案對照（原 `summary`／`description` 誤導，已一併修契約）。
> ⚠️ **RFP 對照**：本項在 RFP **無明文 FR**（`業務功能需求(授權)` 未列）；
> 其依據為**原型 L2 ＋ 業界實務**（stand-in 後送為信用卡核心基本機制）。

**語意**：將**離線／Stand-in 完成之交易**事後補送（advice）進核心，以收斂
「本地代佔」與「額度權威」之落差。**本服務已具備該機制之執行體**
（`StandInUploadService` ＋ `StandInUploadScheduler` ＋ `standin_flag`／`standin_upload_at`），
本端點為其**人工介面**。

| 職責 | 端點 | 行為 |
|---|---|---|
| **查詢待補送佇列／補送結果** | `GET /authorizations/standin?status=PENDING\|UPLOADED\|ALL`（預設 `PENDING`） | 回報 Stand-in 佔用清單（`standin_flag=1`）：`PENDING`＝`standin_upload_at IS NULL`（待補送）／`UPLOADED`＝已上送／`ALL`＝全部。含 `authNo`／決策／金額／`holdId`／交易時間／`standinUploadAt`（非空＝已上送） |
| **手動觸發上送（批次）** | `POST /authorizations/standin/advices` | 立即執行一次「待補送上送」（等同排程之一次執行），回報本次上送筆數與剩餘待送數 |
| **手動觸發上送（單筆）** | `POST /authorizations/{authNo}/advices` | 立即上送該筆（**冪等**：已上送者略過並回報原上送時間） |

> ⚠️ **2026-09-24 補記（實作落差之補正，`CR-023`）**：本表原僅列三項「職責」而**未對應端點**，
> 實作**僅有單筆之 `POST …/{authNo}/advices`** → **「查詢待補送佇列」與「批次手動上送」無端點可達**，
> 致 `SDD-007 §5.4.23`（`/auth/stip` 頁）**無法完整實作**（僅能做「輸入 authNo → 後送」之盲操作）。
> 本節補明端點對應；新增之兩端點依「**規格先行**」**先立於此** → 再改 `tools/gen_openapi.py` 重生契約 → 後實作。
>
> **路徑設計說明**：
> - 清單端點為 `GET /authorizations/standin`（**資源導向**；`status` 以查詢參數篩選），
>   **不**設 `/standin/pending` 之子路徑——因「補送結果」與「待補送佇列」為**同一資源之不同狀態**，
>   拆兩個端點會產生兩處來源（必漂移）。`PENDING`／`UPLOADED`／`ALL` 皆由同一 SQL 加條件。
> - `standin` 為**字面路徑段**，與 `/authorizations/{authNo}` 之模板段並存時，
>   Spring 依「**literal 優先於 template**」匹配；`authNo` 格式固定為 `AUTH` + 6 位數字，**不衝突**。
> - ⚠️ **待補送件之決策不限於 `STANDIN`**——依 `§7.7.1`，`REFERRAL`（及 `APPROVED`）於**本地代佔**時
>   `standin_flag` 亦為 1，故本清單**不得**以 `decision='STANDIN'` 篩選。

**與排程之分工（避免兩條路徑打架）**：

- **排程續行定時上送**（`StandInUploadScheduler`）——不得因本端點而停用；
- **端點為手動補位**（客服／維運發現落差時立即觸發）；
- 兩者**共用同一冪等守門**：`markStandInUploaded(authNo, holdId, at)` 以
  `standin_upload_at IS NULL` 為條件，**重複上送不會產生第二筆佔用**
  （額度側以 `authorizationId` 冪等）。
- ★ **回寫權威 `holdId`（BEAM PMO 2026-10-01 裁定；`#A-129`）**：上送前 `hold_id` 指向
  **本地代佔之 UUID**；上送成功後額度服務回傳**權威 `holdId`**，**於同一交易回寫**
  `authorizations.hold_id`。
  ⚠️ **為何必須**：若**不回寫**，後續 `release` 會用本地 UUID 呼叫額度服務 →
  **查無此佔用** → 降級本地釋放 → **權威端之 hold 永久佔用額度**（**超額風險**）。
  ⚠️ **配套（缺一不可）**：本地投影 `limit_holds` 之同列 `id` 亦須同步為權威 `holdId`
  ——由 `limit.hold.created.v1` 之消費端完成（**改 id、不新增列**，見 `§7.15`）；
  否則 `release(權威 holdId)` 之「本地 best-effort 釋放」將找不到列。
  二者**合起來**才使 `authorizations.hold_id` ＝ 本地列 `id` ＝ 權威 `holdId` **三者一致**。
- ⚠️ **端點不取代排程**：端點是「現在就送」，排程是「保證終會送」；
  若端點失敗（額度服務不可用），**保留待上送狀態**並回報，由排程續試
  （`§7.4`：上送不降級、失敗即拋，保留 `standin_upload_at IS NULL` 待重試）。

#### 7.7.1 `standin_flag` 之判定依據——**依佔用方式，非依決策碼**

> **緣起（2026-09-24 真機實測發現）**：`§7.8` 新增 `REFERRAL` 決策後，`standin_flag`
> 之既有實作（以 `decision == STANDIN` 推導）**不再等價於「是否為本地代佔」**，
> 導致轉人工件在額度服務不可用時**漏標** → **永不後送**。本節明訂判定依據。

`authorizations.standin_flag` 之語意為「**該筆佔用未經額度服務確認**」，
故其判定**必須依佔用方式**（`HoldResult.standIn`，即 `LimitPort.hold` 之回傳旗標），
**不得由決策碼推導**：

| 佔用方式 | 決策 | `standin_flag` | 是否須後送 |
|---|---|---|---|
| 遠端佔用（limit-service 成功） | `APPROVED` | 0 | 否 |
| 遠端佔用 | `REFERRAL` | 0 | 否 |
| **本地代佔（降級）** | `STANDIN` | **1** | **是** |
| **本地代佔（降級）** | **`REFERRAL`** | **1** | **是** ← `§7.8` 新增後之新情境 |

**為何不得由決策碼推導**：

- `§7.8` 之前，`standIn=true` ⟺ `decision=STANDIN`（見 `Authorization.decide`：
  `!approved ? (REFERRAL|DECLINED) : (standIn ? STANDIN : APPROVED)`），
  故以決策碼推導**恰為等價**、無誤。
- **新增 `REFERRAL` 後此等價關係破裂**：轉人工件走 `!approved` 分支，
  該分支**不參考 `standIn`**；但 `holdApplied = approved || referralOnly` 為 true
  → **確實佔額**（且額度服務不可用時即為本地代佔）。
- 若仍以決策碼推導 → 該筆 `standin_flag=0` → `findPendingStandIn`
  （`standin_flag=1 AND standin_upload_at IS NULL`）**永遠查不到它** →
  額度權威側與本地**永久落差** → **超額風險**（`StandInUploadService` 類註解所述之同一風險）。

**實作要求**：`Authorization` 聚合須保留 `standIn` 旗標（`decide(...)` 已接收該參數），
持久化時以之寫入 `standin_flag`；`standIn` 僅於**實際佔用**（`holdApplied`）時為 true，
拒絕件（`DECLINED`）恆為 false。

**契約修訂（本節一併定案，見 `CR-022 §5`）**：

- `summary`：**「後送授權通知」**（原「人工授權與強制授權」**係誤植**——人工／強制授權另立 §7.8）
- `description`：改為後送語意，**移除**「須具 OVERRIDE 權限」（後送不需 OVERRIDE）
- 路徑參數：`{authId}` → **`{authNo}`**（業務代碼，與同服務另兩端點一致）
- 回應碼：**201**（產生補送紀錄，語意正確，不變）
- `requestBody`：**由完整 `AuthorizeRequest` 改為 `AdviceRequest{reason?}`（全部欄位選填）**——
  後送之交易資料已存於本地（`authorizations`），端點**無須重送完整授權請求**；
  `reason` 為選填之稽核參考（**此即 (A) 案與原契約 `AuthorizeRequest` 之關鍵差異**：
  原設計隱含「重送一次交易」，與後送語意不符）

---

### 7.8 人工授權／強制授權（`POST /authorizations/{authNo}/overrides`）——2026-09-24 新增

> **依據（RFP 明文）**：`業務功能需求(授權)` **`S/N 1.1`（授權／發卡授權）第 4 項**：
> 「**支援人工授權、強制授權、人工取消授權**」。
> ⚠️ **廠商回覆欄（[a] Fully comply）已填「滿足」**（原文：「滿足,CAKU的授權檢查規則可以按照需要配置」）
> → **本項為已承諾之需求，未實作即為未履約**。
> 三項之現況：**人工取消授權 ＝ 沖正（§7.6）✅ 已完成**；**人工授權／強制授權 ＝ 本節（新增）**。
>
> **需求追溯**：`RFP FR 1.1`（授權／發卡授權）第 4 項 → 本節 → 端點
> `POST /authorizations/{authNo}/overrides`。

**語意**：交易經檢核後**轉人工**（`REFERRAL`），由具權限之授權中心人員核決
（**核准**＝人工授權；**強制核准**＝`force=true`，略過軟性拒絕）——留審核軌跡。

#### 7.8.1 `REFERRAL` 之產生（轉人工門檻，**參數化**）

> 原型模組 9 之 L2 明列「**開關門檻**」→ 採**參數驅動**，不依賴未實作之風控檢核
> （`CheckCode.RISK`／`DAILY_LIMIT`／`GEO`／`VELOCITY`／`BLACKLIST` 目前
> `isImplemented()=false`，**不得假造檢核結果**，`CSP-001 §8 B2`）。

| 項目 | 內容 |
|---|---|
| 檢核項 | 新增 `CheckCode.MANUAL_REVIEW`（**具資料來源**：參數表，故 `implemented=true`） |
| 門檻來源 | 參數（namespace 如 `AUTH.MANUAL_REVIEW`）：**單筆金額門檻**／**MCC 清單**／**卡種**／**時段** |
| 命中行為 | 該檢核項 `result=FAIL`、`reasonCode=01`（轉人工），決策 `REFERRAL` |
| 佔額 | **仍佔額**（`hold`）——交易已發生，先保留額度；核決 `DECLINE` 時釋放（§7.8.2） |
| 參數未設 | **不轉人工**（預設關閉＝fail-safe：不因缺參數而把正常交易卡住） |

⚠️ **`REFERRAL` 為本節新增之產生點**：查證確認 `AuthDecision.REFERRAL` 於 enum 中存在，
但**全服務原本零產生處**（`CR-022 §4`）；本節為其**唯一產生來源**。

#### 7.8.2 核決（`POST /authorizations/{authNo}/overrides`）

| 欄位 | 規則 |
|---|---|
| `decision` | **`APPROVE`**（人工核准）／**`DECLINE`**（人工婉拒）——2 種列舉 |
| `force` | 選填；`true` ＝**強制授權**（略過軟性拒絕，如超額容差內），須**另具權限** |
| `operatorId` | **必填**——稽核留痕（與 `lifecycle-transitions` 之 `operatorId` 選填不同，**核決必須可歸責**） |
| `reason` | **必填**（≤200） |
| headers | `X-Channel`／`X-Idempotency-Key` 必帶 |

**狀態守門（`SDD-005 §3.1`）**：

| 來源決策 | 允許事件 | 結果 |
|---|---|---|
| `REFERRAL` | `APPROVE` | `APPROVED`（**保留原佔額**） |
| `REFERRAL` | `DECLINE` | `DECLINED`（**釋放原佔額**，呼叫 `LimitPort.release`，§7.6 之同一路徑） |
| `APPROVED`／`DECLINED`／`STANDIN` | 任何 | **`CC-AUTH-3001`**（非轉人工件，不得核決） |
| 已核決（`override_at` 非空） | 任何 | **`CC-AUTH-3002`**（終態不可再核決） |

**稽核**：寫入 `auth_overrides`（決策、`force`、操作者、理由、時間）——**不修改原檢核明細**
（`auth_check_results` 為當時事實；核決為**新增**紀錄，與沖正之「不修改原紀錄」同一原則）。

> ★ **回應型別（`CR-043 §2.2`）**：回 **`OverrideResult`**（`authNo`／`decision`／`force`／
> `holdReleased`／`overriddenAt`）——★ 原契約**誤宣告 `AuthorizeResponse`**；現已改為 `OverrideResult`。

⚠️ **與 (B) 案之區別**：本節**僅允許對 `REFERRAL` 核決**——**不得**對已 `DECLINED` 之件
強制核准（那會繞過風控檢核）。`force=true` 之範圍限於「**軟性拒絕**」
（如超額容差、單筆限額邊界），**不包含**硬性拒絕（卡片 `LOCKED`／`CLOSED`、黑名單）。

### 7.9 稽核軌跡查詢（`GET /authorizations/audits`）——2026-09-29 新增

**緣由**：`SDD-007 §5.1.2.3` 之桶 C「稽核軌跡查詢」（`/system/audit`）原記
「**表存在但無查詢端點**」——本節即為該端點之規格。

**資料來源（既有兩張稽核表，皆為 append-only）**：

| 表 | 產生者 | 內容 |
|---|---|---|
| `auth_overrides` | 人工／強制核決（`§7.8`，`#A-98`） | 決策、`force`、操作者、理由、時間 |
| `auth_reversals` | 沖正（`§7.6`，`#A-95/96`） | 沖正類型、金額、理由、時間 |

**端點**：`GET /authorizations/audits?limit=N&eventType=ALL|OVERRIDE|REVERSAL`

**回應**：`AuthAuditEventList`——**統一時間軸**（兩表 `UNION ALL`，依 `occurredAt` 遞減）。

| 欄位 | 說明 |
|---|---|
| `auditId` | 稽核紀錄識別碼 |
| `eventType` | `OVERRIDE`（人工／強制核決）／`REVERSAL`（沖正） |
| `authNo` | 授權編號 |
| `actor` | **操作者**（核決者 `operatorId`）；**沖正無操作者欄位 → `null`**（如實呈現，**不臆造**） |
| `action` | `APPROVE`／`DECLINE`／`FULL_REVERSAL`／`PARTIAL_REVERSAL` |
| `amountMinor` | 沖正金額（分）；核決事件為 `null` |
| `reason` | 理由 |
| `occurredAt` | 發生時間 |

**設計要點**：

- ★ **統一時間軸**：兩類稽核事件**合併排序**——使用者關心的是「這筆授權發生過什麼」，
  而非「它屬於哪張表」；分開兩頁會迫使使用者自行拼湊時間序。
- ⚠️ **`actor` 之誠實揭露**：`auth_reversals` **無操作者欄位**（該表未記錄）→ **回 `null`，不臆造**；
  畫面顯示「未記錄」。若要補齊，須於沖正路徑加 `operator_id`（**列為後續，另立 CR**）。
- ⚠️ **append-only 唯讀**：本端點**僅查詢**，**不提供修改／刪除**——
  稽核紀錄之**可竄改性將使其失去證據力**。
- **`limit` 預設 20、上限 200**（避免一次拉全表）。
- **服務範疇（誠實揭露）**：本端點涵蓋**授權服務**之稽核軌跡。
  其餘服務之稽核（如 `party` 管制碼異動、`card` 生命週期）**尚未有查詢端點**，
  待各自就緒後擴充（`C-46`）。

### 7.10 統計端點（`GET /api/v1/authorizations/stats`）——2026-10-01 新增（`CR-026`）

> **緣由**：`CR-026`（營運總覽之業務 KPI 統計端點）。`/dashboard` 之「業務 KPI」區塊需各服務提供統計端點——經查 17 服務皆無，故本節為規格先行之補建。

**端點**：`GET /api/v1/authorizations/stats` → `AuthorizationStats`（★ `CR-043 §3.1` #2：原 `AuthorizationStatsView`，與實作 `domain/model/AuthorizationStats` 統一）

| 欄位 | 來源 | 說明 |
|---|---|---|
| `total` | `COUNT(*)` | 授權總數（由後端計算；前端不得自行相加分組） |
| `today` | `COUNT(*) WHERE 授權日 = 業務日` | 當日授權數（`decision` 不篩選——含核准／拒絕／轉人工／stand-in） |
| `byDecision` | `GROUP BY decision` | `APPROVED`／`DECLINED`／`REFERRAL`／`STANDIN`（**DDL CHECK 之 4 值**，不補 0） |
| `pendingUpload` | `COUNT(*) WHERE standin_flag=1 AND standin_upload_at IS NULL` | **待後送件數**（依 `V4__standin_upload.sql` 權威註解；stand-in 佔用尚未上送對帳者） |

**實作要求**（與 `SCS-003 §7.9`、`SCS-012 §7.x` 一致）：

1. 以 `GROUP BY` 聚合（單次查詢），不得 N+1。
2. `total`／`today` 由後端計算，不得由前端相加分組。
3. `byDecision` 僅含實際存在之值（不補 0）。
4. 不快取、無查詢參數（本階段為總量統計）。

### 7.11 交易狀態機檢視（`GET /api/v1/authorizations/{authNo}/state`）——2026-10-01 新增（桶 C 解鎖）

> **緣由**：`SDD-007 §5.1.2.3` 之桶 C「**交易狀態機與冪等**」（`/switch/auth/state`）原記「均無端點」。本節為該端點之規格先行補建（比照 `#A-101`／`#A-102`／`#A-113` 模式）。

**端點**：`GET /api/v1/authorizations/{authNo}/state` → `AuthorizationState`（★ `CR-043 §3.1` #3：原 `AuthorizationStateView`，與實作統一）

**語意**：對**單一授權交易**呈現其**狀態機視角**——當前狀態、佔用狀態、狀態轉移歷程。

| 欄位 | 來源 | 說明 |
|---|---|---|
| `authNo` | `authorizations.auth_no` | 授權編號 |
| `currentState` | `authorizations.decision` | 當前狀態（`APPROVED`／`DECLINED`／`REFERRAL`／`STANDIN`；DDL CHECK 4 值） |
| `respCode` | `authorizations.resp_code` | 回應碼 |
| `standinFlag` | `authorizations.standin_flag` | 是否本地代佔（**該筆佔用未經額度服務確認**，`§7.7.1`） |
| `standinUploadAt` | `authorizations.standin_upload_at` | 後送時間；`null`＝**待後送** |
| `hold` | `limit_holds`（以 `authorizations.hold_id` 關聯） | 佔用狀態；**無佔用者 `null`** |
| `transitions` | `authorizations` 之 `created_at`／`override_at`／`reversed_at` | 狀態轉移歷程（時間遞增） |

`hold` 子物件：`holdId`／`holdAmountMinor`／`releasedAmountMinor`／`status`／`expiresAt`（來源 `limit_holds`）。

`transitions[]` 元素：`occurredAt`／`event`（`AUTHORIZED`／`OVERRIDDEN`／`REVERSED`）／`toState`／`detail`。

**設計要點**：

- ★ **狀態機視角 vs 事件時間軸**：本頁聚焦「**這一筆**授權現在的狀態與其轉移歷程」；`§7.9` 稽核軌跡為「**所有**授權之核決／沖正事件統一時間軸」——兩者互補、**不重複**。
- ⚠️ **不以 `decision` 推導「是否曾核決／沖正」**：改用 `override_at`／`reversed_at`（**權威時間欄**）判定，避免與 `§7.7.1` 同型之推導缺陷。
- ⚠️ **冪等之誠實揭露**：`X-Idempotency-Key` 之冪等由 **API 層**（Redis）保證，**未落 Oracle** → 本頁**不重現**冪等明細，**不臆造欄位**（機制見 `SCS-022`／`CR-010`）。
- **不快取**；查無 `authNo` → **`404`**。
- **服務範疇**：涵蓋 `authorization-service` 之授權交易。

### 7.12 卡片狀態事件消費（`card.card.status-changed.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 第一切片）

> **緣由**：`V2__read_model.sql` 明文載「`card.card.status-changed.v1` → `card_status_caches`」，
> 惟**從未有人餵養**該讀模型——`JdbcCardPort` **讀**之，卻無任何**寫入者**（**讀模型恆空／過期**）。
> 本節為該**消費端**之規格（契約見 `SAD-004 §5.4`）。

**訂閱**：主題 **`cc.card.card.status-changed`**（命名規則見 `SAD-004 §5.4`）；群組 `authorization-service`。

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

| 欄位 | 來源（card-service `cards`） |
|---|---|
| `cardNoHash` | `card_no_hash`（**比對鍵**，讀模型之唯一鍵） |
| `cardId` | `id` |
| `accountId` | `account_id` |
| `cardNoMasked` | `card_no_masked` |
| `status` | `status` |
| `blockCode` | `block_code` |
| `expiryDate` | `expiry_date` |

**處理**：

1. **冪等**：`eventId` 寫入 `consumed_events`（**與 upsert 同交易**）；已存在 → 略過。
2. **亂序防護**：`sequenceNo` 落後於該 `cardNoHash` 已套用者 → 略過並 WARN。
3. **Upsert**：以 `card_no_hash`（`TRIM` 比對，同 `JdbcCardPort`）為鍵 upsert `card_status_caches`。
4. **未知 `eventType`**：略過並 WARN（**不拋錯**，`SAD-004 §5.4`）。

**設計要點**：

- ★ **讀模型為「本地投影」、非權威**——權威仍在 `CC_CARD.CARDS`；本消費使授權之**降級路徑**（遠端不可用時讀本地）**不致讀到過期狀態**。
- ⚠️ **不覆寫較新狀態**（亂序略過）——避免「舊事件蓋掉新狀態」之**靜默錯誤**。
- **不快取**。

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

| 驗證 | 結果 |
|---|---|
| 訂閱 | ✅ 啟動日誌 `partitions assigned: [cc.card.card.status-changed-0]` |
| 遷移 | ✅ Flyway `Successfully applied 1 migration to schema "CC_AUTH", now at version v7` |
| 首播（**缺口證明**） | ✅ **BEFORE `card_status_caches` count=0**（讀模型**本為空**，證實「無人餵養」） |
| 套用 | ✅ 發布事件 → AFTER `****9999\|SUSPENDED\|LOCKED\|seq=1`（**已 upsert**）；`consumed_events` 落列 |
| **冪等** | ✅ 重送**同 `eventId`** → `version` **仍 0**、`consumed_events` **仍 1 列**（未重複套用） |
| **亂序防護** | ✅ 送 `sequenceNo=0`（較舊、狀態 `ACTIVE`）→ 讀模型**仍 `SUSPENDED\|LOCKED\|seq=1`**（**未被子舊事件覆寫**） |
| 單元測試 | ✅ `CardStatusProjectionServiceTest` 3 例（含**未知型別略過不拋錯**） |

> ✅ **生產端已實作（2026-10-01，`#A-118`）**：`card-service` 於 `lifecycle-transitions` 時，
> 於**同一交易**寫 `outbox`，由 relay（`OutboxRelayScheduler` ＋ `KafkaEventPublisher`）發布
> （見 `SCS-003 §7.10`）。
> **端到端真機驗證**：`LOCK` → 本讀模型 `SUSPENDED|LOCKED|seq=1`；`UNLOCK` → **`ACTIVE|ACTIVE|seq=2|v=2`**。
>
> ⚠️ **未竟（誠實揭露）**：**死信佇列（DLQ）**、**消費延遲指標**、**事件重放工具**尚未實作。

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

> **緣由**：`V2__read_model.sql` 明文載「`limit.limit.changed.v1` → `account_limit_caches`」，
> 惟**從未有人餵養**（同 `§7.12` 之卡片狀態讀模型——**讀模型恆空／過期**）。
> 本節為該**消費端**之規格（契約見 `SAD-004 §5.4`；生產端見 `SCS-002 §7.5`）。
>
> ⚠️ **命名落差之處理**：`V2` 註解寫 `limit.limit.changed.v1`，惟 `SAD-004 §5.2`（**事件清單，權威**）載為
> **`limit.limit.adjusted.v1`**。本規格**依 `SAD-004 §5.2`**（事件清單為契約層、`V2` 註解為說明性文字）。

**訂閱**：主題 **`cc.limit.limit.adjusted`**（命名規則見 `SAD-004 §5.4`）；群組 `authorization-service`。

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

| 欄位 | 來源（limit-service `account_limits`） | 對應讀模型欄位 |
|---|---|---|
| `accountId` | `account_id` | `account_limit_caches.account_id`（**比對鍵之一**） |
| `currency` | `currency` | `account_limit_caches.currency`（**比對鍵之一**） |
| `totalAmountMinor` | **套用後之總額度**（永久 ＋ 有效臨時 ＋ 彈性） | `account_limit_caches.total_amount` |

**處理**：

1. **冪等**：`eventId` 寫入 `consumed_events`（**與 upsert 同交易**）；已存在 → 略過。
2. **亂序防護**：`sequenceNo` 落後於該（`account_id`, `currency`）已套用者 → 略過並 WARN。
3. **Upsert**：以（`account_id`, `currency`）為業務鍵 upsert `account_limit_caches`；`currency` 為 `CHAR(3)` → 比對一律 `TRIM`。
4. **未知 `eventType`**：略過並 WARN（**不拋錯**，`SAD-004 §5.4`）。

**設計要點**：

- ★ **只更新 `total_amount`、不動 `used_amount`**：`used_amount` 之權威在帳務（`SCS-002 §10 #3`），本消費**不得**臆測。
- ⚠️ **既有列之保護**：`account_limit_caches` 可能由**本地代佔路徑**（`JdbcLimitPort`）讀取；本 upsert **僅覆寫 `total_amount` 與序號**，`used_amount` **原值保留**。
- ⚠️ **不覆寫較新狀態**（亂序略過）。
- **不快取**。

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

| 驗證 | 結果 |
|---|---|
| 訂閱 | ✅ 啟動日誌 `Subscribed to topic(s): cc.limit.limit.adjusted`；`partitions assigned: [cc.limit.limit.adjusted-0]` |
| 遷移 | ✅ Flyway `Successfully validated 9 migrations`（含 `V8`） |
| 缺口證明 | ✅ **BEFORE**：`account_limit_caches` 既有 3 列之 `last_sequence_no` **皆為 0**（**非由消費者寫入** → 證實該讀模型**本無餵養者**） |
| 生產端（端到端） | ✅ limit-service `POST /limit-adjustments`（10,000 → 410,000 分、`EFFECTIVE`）→ `outbox` 落列（`limit.adjusted`）→ relay 標記 `published_at` → 本讀模型 `total_amount` **10,000 → 410,000**、`version` 0 → **1** |
| 冪等 | ✅ 重送**同 `eventId`** → `version` **仍 2**、`consumed_events` 總數**不變**；日誌 `額度事件未套用（重複或亂序，已略過）：eventId=E2E-IDEM-1` |
| 亂序防護 | ✅ 送 `sequenceNo=1000`（落後，`totalAmountMinor=999`）→ 讀模型**仍 500,000**（**未被舊事件覆寫**）；日誌同上（`eventId=E2E-OOO-1 seq=1000`） |
| 未知型別不毒化 | ✅ 期間一則無法辨識之訊息 → WARN 略過、**消費者未中斷**（後續訊息照常處理） |
| 單元測試 | ✅ `AccountLimitProjectionServiceTest` 4 例（含**未知型別略過不拋錯**、**跨事件型別守門**） |

> ✅ **生產端已實作（2026-10-01，`#A-119`）**：`limit-service` 於額度調整**生效**時，
> 於**同一交易**寫 `outbox`，由 relay 發布（見 `SCS-002 §7.5`）。
> ⚠️ **未竟（誠實揭露）**：**撤銷／到期**路徑未發射事件（`SCS-002 §10 #5`）；DLQ／消費延遲指標／事件重放工具尚未實作。

### 7.14 授權核准事件輸出（`authorization.authorization.approved.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 推廣・事件 #3）

> **緣由**：`SDD-003` 之授權時序圖（「`Authz->>Outbox: 寫入 authorization.authorization.approved.v1`」）
> 與 `SDD-006 §4.2`（payload 欄位）**均已載明本事件**，惟**從未有人發射**——
> 同 `§7.12`／`§7.13` 之「**文件已載、程式全無**」型缺口。
> 本節為該**生產端**之規格（**消費端**為 `posting`／`limit`／`notification`，`SAD-004 §5.2`——**待下一片**）。

**主題**：`cc.authorization.authorization.approved`（命名規則 `SAD-004 §5.4`）。

| 項目 | 值 |
|---|---|
| `eventType` | `authorization.authorization.approved.v1` |
| Outbox `aggregate_type` | `authorization` |
| Outbox `event_type` | `authorization.approved`（**不含**版本） |
| Kafka 分區鍵 | `authorizationId`（**同一授權之事件落同一分區 → 聚合內有序**） |

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

| 欄位 | 來源 | 說明 |
|---|---|---|
| `authorizationId` | `authorizations.id` | 授權識別碼（**亦為信封 `aggregateId`**） |
| `authNo` | `authorizations.auth_no` | 授權編號（對外可見） |
| `cardId` | `authorizations.card_id` | 卡片識別碼；查無卡片時為 `null` |
| `accountId` | `authorizations.account_id` | 歸戶識別碼；查無卡片時為 `null` |
| `amount` | `authorizations.amount` | **最小單位（分）**（`SDD-006 §2.5`） |
| `currency` | `authorizations.currency` | ISO 4217 |
| `holdId` | `authorizations.hold_id` | 額度佔用識別碼。⚠️ **核准必然佔用額度**（`Authorization.decide`：`holdApplied`）→ **本事件之 `holdId` 恆非 `null`**；仍保留 `null` 之序列化處理作為防禦 |
| `holdAmount` | `authorizations.hold_amount` | **最小單位（分）**。⚠️ 聚合之 `holdAmount` **恆非 `null`**——未佔用時為 `Money.zero`（**`0`**）→ **不以 `null` 表示「未佔用」**（「佔用 0 元」與「未佔用」在本事件中**不會並存**，因僅核准路徑發射） |
| `standinFlag` | `authorizations.standin_flag` | 是否本地代佔（`§7.7.1`：依**佔用方式**，非依決策碼） |
| `cardNoMasked` | `authorizations.card_no_masked` | **2026-10-01 新增之可選欄位**（`#A-125`）——供通知服務之 `AUTH_APPROVED` 樣板取 `cardLast4`。**已遮罩，非明文**；先例見 `card.card.status-changed.v1` |
| `merchantId` | `authorizations.merchant_id` | **2026-10-01 新增之可選欄位**（`#A-125`）——同上，供樣板之 `merchant`。**非敏感**（商戶代號） |

> ⚠️ **末兩欄為契約擴充**：依 `SAD-004 §5.3`「**新增可選欄位不升版**」→ **不升 `v2`**；
> 惟**仍屬契約擴充**，已列 `CR-027 §5 #11` **待裁定**。
> 緣由：`AUTH_APPROVED` 樣板（**既有種子資料**）需要 `cardLast4`／`merchant`，
> 而原 payload 未帶——見 `SCS-016 §7.9`。

**處理**：

1. **同交易寫 Outbox**：`AuthorizeUseCase.execute` 於 `repository.save(authorization)` **之後**、**同一交易**（類別層 `@Transactional`）寫入 `outbox`；否則會出現「**授權已核准、事件未發**」之落差。
2. ★ **發射條件＝「是否佔用額度」**（`Authorization.holdsLimit()`）——即 `decision ∈ {APPROVED, STANDIN, REFERRAL}`。
   **本事件之語意重新界定為「授權已佔用額度」**（`CR-030 §2 #8` 裁定）：
   - `APPROVED`／`STANDIN`——正常核准／代授權；
   - **`REFERRAL`（轉人工）——亦佔用額度**（見上方 `willApprove` 之判定與 `limitPort.hold` 之呼叫），
     故**必須發射**，否則下游**無從得知該筆佔用**。
   ⚠️ **原以 `AuthDecision.isApproved()`（不含 `REFERRAL`）為條件**——造成「**額度被佔用而下游完全不知**」之**真缺陷**，已更正。
   ⚠️ **`DECLINED` 不佔用額度 → 不發射任何事件**：拒絕率／原因分佈改以 **metrics** 涵蓋（`CR-030 §2 #7`）。
3. **payload 補 `decision`（可選）**（`CR-030 §2 #8`）——下游據此分辨「核准」與「**轉人工待覆核**」。
   ⚠️ `notification-service` 對 `decision = REFERRAL` **不建立通知**（無對應樣板，**不臆造文案**，見 `SCS-016 §7.9`）。
4. **relay 發布**：`OutboxRelayScheduler` 週期性呼叫 `common-event` 之 `OutboxPublisher`（`SDD-004 §4.3`）。

**設計要點**：

- ★ **`standinFlag` 依「佔用方式」而非決策碼**（`§7.7.1` 之既有鐵律）——直接取聚合之 `standIn()`，**不由 `decision` 反推**。
- ⚠️ **`DECLINED` 不在本節**：拒絕事件為 `authorization.authorization.declined.v1`（`SDD-006 §4.2`）——**尚未發射**（見 §10 #6）。
- ⚠️ **序列化為 `StringSerializer`**（Outbox `payload` 已為信封 JSON 字串；`JsonSerializer` 會多包引號，消費端解析失敗——`SAD-004 §5.4`）。

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

| 驗證 | 結果 |
|---|---|
| 遷移 | ✅ `CC_AUTH.outbox` 建立成功（`V9`；查詢得 0 列＝**BEFORE 無餵養者**） |
| 生產（核准件） | ✅ `POST /api/v1/authorizations`（15,000 分、`APPROVED`）→ `outbox` **落 1 列**（`authorization`／`authorization.approved`）→ relay **標記已發布**（`published=YES`、`retry_count=0`） |
| Kafka | ✅ 主題 `cc.authorization.authorization.approved` **自動建立**；消費得信封：`eventType`／`aggregateId`（＝`authorizationId`）／`sequenceNo`／`traceId`／`schemaRef` **皆正確** |
| payload 契約 | ✅ `authorizationId`／`authNo`／`cardId`／`accountId`／`amount`＝15000／`currency`＝TWD／`holdId`／`holdAmount`＝15000／`standinFlag`＝false——**與 `SDD-006 §4.2` 逐欄一致**（金額為**最小單位**） |
| **★ `REFERRAL` 不發射（負樣本）** | ✅ 送 25,000 分（≥ 門檻 20,000）→ `decision=REFERRAL`、**已佔用**（`holdId` 非空）→ **`outbox` 仍為 0 列**——證實 `isApproved()` 不含 `REFERRAL`（**此即 §10 #8 之落差**） |
| 單元測試 | ✅ `AuthEventFactoryTest` 4 例（信封固定欄位／payload 契約欄位名／**`standinFlag` 依佔用方式**／序號嚴格遞增） |

### 7.15 佔用事件消費（`limit.hold.created.v1`／`limit.hold.released.v1`）——2026-10-01 新增（`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 B）

> **緣由**：`V2__read_model.sql` 明文載「`limit_holds` 為**額度佔用之本地投影**（權威在 `CC_LIM.LIMIT_HOLDS`）」，
> 惟**從未有人餵養**——`JdbcLimitPort.hold()` 僅於 **stand-in 降級路徑**寫入；**正常路徑（遠端佔用成功）
> 完全不寫** → 本地投影**缺列**（同 `card_status_caches`／`account_limit_caches` 之型，此為**第三個**）。
> 訂閱關係與契約之化解見 `CR-028`（`SAD-002 §3.1` 已補為訂閱 `limit.hold.*` 二者）。

**訂閱**：主題 **`cc.limit.limit.hold.created`** ＋ **`cc.limit.limit.hold.released`**；群組 `authorization-service`。

**處理**：

1. **冪等**：`eventId` 寫入 `consumed_events`（**與投影寫入同交易**）；已存在 → 略過。
2. **亂序防護**：`sequenceNo` 落後於該 `aggregateId` 已消費之最大值 → 略過並 WARN。
   ⚠️ **判準取自 `consumed_events`**（`MAX(sequence_no) WHERE aggregate_id = ?`），**不**在 `limit_holds` 增設技術欄位
   ——理由見下表。
3. **`created` → upsert 本地投影**（**★ 以 `authorizationId` 為業務鍵去重**）。
   ★ **`instrumentId` 落地（`CR-054` P2，2026-10-17）**：事件帶之載體寫入
   `limit_holds.instrument_id`（`V14` 新增，**可空**——`V10` 前之歷史佔用無載體）。
   ⚠️ 更新既有列時用 **`instrument_id = NVL(?, instrument_id)`**：事件未帶載體時
   **不得清空**既有值——否則事件重放（或舊事件）會把已補的載體抹掉。
4. **`released` → 累加 `released_amount`**：以 `holdId` 定位本地列；
   `released_amount = LEAST(hold_amount, released_amount + ?)`；
   ★ `status` → `RELEASED`（全額）／**全額前維持 `HELD`**（`CR-044 ③`）。
   > ⚠️ **原實作於部分釋放寫 `PARTIALLY_RELEASED`**——與權威（`limit-service`）不一致，
   > 致「同一佔用兩種陳述」（權威 `HELD`、讀模型 `PARTIALLY_RELEASED`）。
   > BEAM PMO 2026-10-15 裁定「**以 limit 為權威、讀模型鏡射**」→ ★ 改為**鏡射權威語意**
   > （`status = CASE WHEN released_amount + ? >= hold_amount THEN 'RELEASED' ELSE status END`）。
5. **未知 `eventType`**：略過並 WARN（**不拋錯**，`SAD-004 §5.4`）。

#### ★★ 設計要點（**每一項皆為避免「重複計算額度」之具體防護**）

| 決策 | 理由 |
|---|---|
| **★★ `created` 以 `authorizationId` 去重；且**已有同授權之列時「改其 `id` 為權威 `holdId`」**（而非略過）** | ⚠️ **關鍵風險**：`StandInUploadService` 上送時會呼叫 limit-service **建立權威 hold**（→ 發 `created` 事件），**而本地早已有一列**（stand-in 當下寫的、`id` 為本地 UUID）。若消費端以 `holdId` 盲目插入 → **同一筆佔用兩列** → `JdbcLimitPort.availableLimit` 之 `SUM(hold_amount − released_amount)` **重複扣減** → **可用額度被低估**。故：**不新增列**。⚠️ **但亦不可「純略過」**——BEAM PMO 2026-10-01 裁定「上送成功後回寫 `authorizations.hold_id` 為權威 `holdId`」（`§7.7`），若本地列 `id` 不同步，`release(權威 holdId)` 之本地 best-effort 釋放將**找不到列** → 本地投影永久 `HELD`。→ **故改其 `id`（＋ `hold_amount`）為權威值**，使三者一致 |
| **正常路徑（遠端佔用）則會寫入** | 遠端佔用成功時本地**無列** → 插入（`id` ＝**權威 `holdId`**）→ 本地投影**終於有餵養者** |
| **亂序判準置於 `consumed_events`** | `limit_holds` 為**業務表**（stand-in 路徑亦寫入），**不應**為事件消費增設技術欄位——與 `§7.12`／`§7.13`（該二處之目標表為**純讀模型**）情況**不同**；同一理由已見於 `SCS-002 §7.7` |
| **`released` 找不到列 → 略過並記 INFO** | stand-in 情境之本地列 `id` 為**本地 UUID**，權威 `released` 事件之 `holdId` 對不上 → 略過（該情境之釋放已由 `StandInLimitPort.release` 之**降級路徑**處理，見該類別註解） |
| **不快取** | — |
| **幣別之決定** | `limit_holds.currency` 為 `NOT NULL`，惟事件之 `currency` 為**可選**欄位（2026-10-01 補）→ 實作：**事件帶者優先**；未帶時**回查 `account_limit_caches`（須恰一列）**；仍無法決定 → WARN 並略過寫入 |
| **`holdType` 不入本地投影** | `CC_AUTH.LIMIT_HOLDS` **無 `hold_type` 欄位**——刻意不為它增欄（本投影之用途為**可用額度計算**，不含佔用類型） |

> ⚠️ **未竟（誠實揭露）**：**(i)** stand-in 後送成功後，**權威端之 hold 不會被後續 `release` 釋放**
> （`authorizations.hold_id` 仍指向**本地 UUID**）——**既有落差**，非本節引入；
> **(ii)** 本地與權威之 `holdId` 於 stand-in 情境**不一致**（本節以 `authorizationId` 去重即為此）；
> **(iii)** DLQ／消費延遲指標／事件重放工具未實作。

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

| 驗證 | 結果 |
|---|---|
| 訂閱 | ✅ `partitions assigned: [cc.limit.limit.hold.created-0, cc.limit.limit.hold.released-0]` |
| **Test A：正常路徑** | ✅ 送授權（15,000 分、`APPROVED`）→ 遠端佔用成功 → 事件 → **本地 `limit_holds` 8 → 9**（`consumed_events` 8 → 9）；新列 `id` ＝**權威 `holdId`**、`authorization_id` 正確、`currency`＝TWD（**由事件帶入**）、`hold_amount`＝15000、`status`＝`HELD` |
| **★ Test B：stand-in 去重（關鍵風險）** | ✅ **停 limit-service** → 送授權（20,000 分）→ 降級 **stand-in**（本地列 `id`＝**本地 UUID**）→ **重啟 limit-service** → `StandInUploadScheduler` 上送（`CC_LIM.limit_holds` 25 列、outbox 6 列）→ 事件 → 消費端 **`本地投影已有同授權之佔用列，略過插入（避免重複計算）`** → **該 `authorizationId` 之列數＝1（非 2）**、本地總數**仍 10**（未增加）——**證實「同一筆佔用兩列 → 重複扣減」之風險已被阻斷** |
| **★ `availableLimit` 之 status 過濾修正** | ✅ 造一列 `PARTIALLY_RELEASED`（`hold_amount` 15,000／`released_amount` 5,000）後，**修正前** `SUM`（僅 `HELD`）＝**86,000**、**修正後**（`HELD`＋`PARTIALLY_RELEASED`）＝**96,000**——差額 **10,000** 即該列之未釋放餘額；證實**原 SQL 低估佔用、高估可用額度**（**超額風險**） |
| 單元測試 | ✅ `LimitHoldProjectionServiceTest` **7 例**（含 **stand-in 改 id**、亂序、冪等、型別守門） |
| **★ Test C：回寫權威 `holdId` 後之「三者一致」（BEAM PMO 2026-10-01 裁定；`#A-129`）** | ✅ **停 limit-service** → 送授權（12,000 分、`STANDIN`）→ 本地 `holdId`＝`a5280009-…`（**本地 UUID**）→ **重啟** → 排程上送（`standin_upload_at`＝11:57:31）→ 事件 → 消費端**改本地列 `id`**。**結果三者完全一致**：`authorizations.hold_id` ＝ 本地 `limit_holds.id` ＝ **`2B72AB60…CAA4E7`**（**權威 `holdId`**）；該授權之列數＝**1**（不重複）、本地列 `version` 0 → **1**（**UPDATE 而非 INSERT**）。**★ 原本地 UUID 已被取代**——證實「回寫 ＋ 改 id」之配套**同時生效** |

#### ★ 順手修正之既有缺陷（`JdbcLimitPort.availableLimit`）

`AVAILABLE_SQL` 原僅計 `status = 'HELD'`，惟 `release()` 於**部分釋放**時會將狀態改為 `PARTIALLY_RELEASED`
——該列之未釋放餘額**仍屬佔用**卻被排除 → **低估佔用、高估可用額度 → 超額風險**。
已改為 `status IN ('HELD', 'PARTIALLY_RELEASED')`（終態 `RELEASED`／`EXPIRED` 之未釋放餘額為 0，本即無影響）。

### 7.16 額度池選擇（專用額度；POC-8 之 8-3／8-4／8-5）——2026-10-02 新增（`CR-035 §2.2`）

> **緣由**：`CR-014 v1.1` 完成後專用額度**可設定但不能動用**——本服務只查卡人額度，
> 專用額度完全不參與 → `BR-04-01`（不佔卡人額度）與 8-5（超額拒絕）皆不成立。

#### 7.16.1 三段式池選擇

```
① 查「適用之專用額度」（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'
```

★ **`BR-04-01` 為結構性成立**：專用池之佔用**只寫 `limit_holds`（`special_limit_id` 非空）
與 `special_limits.used_amount`**，**完全不動 `account_limits`** ——
故下游之 `available = total − used − holds` 天然不含該筆，**非靠事後沖銷**。

> ⚠️ **需求語意之裁定（`CR-035 §2.2`）**：`BRD-004` 之 8-4 明訂「**未設定**專用額度」才回歸卡人額度；
> 8-5 則為「繳稅金額 &gt; 額度 → 拒絕」。本實作採**三段式（含回退）**，
> 理由：**設定專用額度不得使客戶之交易能力反而下降**
> （否則有 5 萬專用額度者會比完全沒設定者更差）。此為**對需求之解釋**，非隱含假設；
> 如客戶採「嚴格解釋（已設定即不得回退）」，只需調整 `AuthorizeUseCase#checkLimit` 一處。

#### 7.16.2 埠與降級

| 項目 | 內容 |
|---|---|
| 埠 | `SpecialLimitPort#findApplicable(accountId, currency, mcc, amountMinor)` |
| 實作 | `RemoteSpecialLimitPort`（即時呼叫 `limit-service` 之適用性端點） |
| 裝飾 | `StandInSpecialLimitPort`（`@Primary`）——**失敗即回 `empty`**（視為「無適用」） |

★ **降級為安全降級**：`limit-service` 不可用 → 本筆**退回卡人額度**（8-4），
**不會超額放行專用池**。代價是 **Stand-in 期間專用額度不可用**——**如實揭露**，不假裝有。
★ **不建本地投影**：專用額度之定義與用量皆在 `CC_LIM`，維護投影只為 Stand-in 而存在，
卻引入「兩份定義漂移」之風險（`CR-001 §7` 之權型）→ **刻意不做**。

★ **專用池之佔用於 Stand-in 期間 fail-closed**：`JdbcLimitPort#hold` 收到 `specialLimitId` 非空時
**拋出業務例外**（`CC-LIM-1003`）——若以卡人額度代佔，將**同時**違反 `BR-04-01` 並造成**雙重佔用**。

#### 7.16.3 真機驗證（2026-10-02）

見 `SCS-002 §7.10.4`（同一輪驗證，11 項）。摘要：8-3 之 `availableLimit` **不因專用佔用而減少**
（310,000 保持）而 8-4 之卡人池確有扣減（310,000 → 295,000）；`account_limits` **全程未變**；
8-5 → **DECLINED 51**。

### 7.17 授權請求之特店名稱／輸入模式／EMV 資料之接收（`CR-043 §2.3`）——2026-10-15 新增

★ **緣由**：契約 `AuthorizeRequest` 宣告 `merchantName`／`entryMode`／`emvData` 三欄，
惟實作 DTO **無此三欄**——消費端依契約送此三欄會被**靜默忽略**（`CR-043 §2.3`、`#A-187` ⑥）。
BEAM PMO 2026-10-15 裁定「**請補實作接收**」（選 `CR-043 §4` 之 **(B)** 案，非「修契約移除」）。

| 契約欄位 | 型別／值域 | DDL 欄位 | 可空 |
|---|---|---|---|
| `merchantName` | `string`，`maxLength 80` | `authorizations.merchant_name VARCHAR2(80 CHAR)` | 是 |
| `entryMode` | `enum`：`CHIP`／`MAGSTRIPE`／`CONTACTLESS`／`EC`／`MANUAL`／`QR` | `authorizations.entry_mode VARCHAR2(16 CHAR)` | 是 |
| `emvData` | `object`（自由物件，如 `{"arqc":"A1B2C3D4"}`） | `authorizations.emv_data JSON` | 是 |

★ **DDL 事實（動查）**：`merchant_name`／`entry_mode` **早已存在於 `V1__baseline.sql`**
（← `SDD-001 §3`）——惟 `JdbcAuthorizationRepository` 之 INSERT **從未綁定**
（`merchant_name` 未列入、`entry_mode` 綁死 `null`）→ 兩欄**恆為 NULL**，即「**欄位在、資料不進**」。
`emv_data` 則為**新欄**，由 **`V10__authorization_entry_emv.sql`** 新增。

★ **落庫語意**：
1. `merchantName`／`entryMode` 以字串直接綁定（`entryMode` 之值域由契約 enum ＋ 介面層
   `@Pattern` 把關；DDL **無** CHECK，故**不**於資料庫層再設值域——維持與既有 `mcc` 一致之寬鬆）。
2. `emvData` 以 **Jackson `ObjectMapper`** 序列化為 JSON 字串，經 **`JSON(?)`** 綁定
   （Oracle 23ai `JSON` 型別；與 `JdbcPartyRepository.FLAGS` **同型**）。
   ⚠️ 讀取（若需）以 **`JSON_SERIALIZE(emv_data RETURNING VARCHAR2(4000))`**——**不可**直接
   `rs.getString` 取 `JSON` 欄（型別不符）。
3. **`emvData` 未做遮罩**：其為裝置端 EMV 資料（ARQC／ATC 等），**不含卡號明碼**
   （卡號另以 `cardNo` 欄位加密傳輸）——故不另遮罩，如實落庫。

★ **回應不擴充**：`AuthorizeResponse`（及 `GET /{authNo}` 之明細）契約**未**宣告此三欄，
故**本期不加入回應**——避免「契約外欄位」造成契約↔實作漂移（`CR-019` 之教訓）。
接收＝**落庫可稽核**，非「回吐」。

★ **測試**：`AuthorizationRepositoryIntegrationTest` 新增**直查 DB** 之往返測試
（`SELECT merchant_name, entry_mode, JSON_SERIALIZE(emv_data ...) FROM authorizations`）——
證明三欄**確實落庫**（非僅 DTO 有欄位＝**防假綠**）。

### 7.18 卡片級額度**投影查詢**（`GET /api/v1/limit-projections/accounts/{accountId}/instruments`）——2026-10-17 新增（`CR-052` C-2(b)／`CR-054` P3）

> **緣由**：`CR-052 §4` BEAM PMO 裁定 C-2＝**(b) 事件投影**；`CR-054` 盤點後確認投影之**唯一可行範圍**
> 為「每卡未釋放佔用」（事件可重建），並經 BEAM PMO 同意 Q1～Q4 後落地 P1（事件補欄）→ P2（投影加欄）→ P3（查詢＋告警）。

#### 7.18.1 ★★ 語意邊界（風控底線，不可逾越）

| 用途 | 走哪裡 |
|---|---|
| **授權決策**（佔用／可用額度判斷） | **權威即時**：`limit-service` 之 `account_limits`＋`special_limits`＋`limit_holds` |
| **人讀查詢**（客服／報表／App） | 本端點（**投影**，可能落後） |

> ★ **本端點不得供授權使用**（`CR-052 §4.1`，BEAM PMO 已確認）。額度正確性為風控底線——寧可查詢慢。

#### 7.18.2 ★ 投影**只**涵蓋「每卡未釋放佔用」（誠實揭露）

| 分量 | 是否在投影 | 原因 |
|---|---|---|
| 每卡未釋放佔用 | ✅ | 由 `limit.hold.created/released` 事件重建（P1 補 `instrumentId`） |
| 每卡**主額度** | ❌ | 在 `limit-service` 之 `CARD` 維度（`V11`）；卡片級調額**刻意不發**帳戶事件（`SCS-002 §7.16.3`）→ 本服務無事件來源 |
| 每卡**已用額度** | ❌ | 權威為**帳戶層** `used_amount`，**無 per-card 來源** |
| 每卡**可用額度** | ❌ | ＝主額度 − 已用 − 佔用；前二者皆不在投影 → **不提供**（不以部分分量拼湊） |

> ★ 若客戶要求「投影之每卡可用額度」，須先**將卡片額度事件化**（另立事件、凍結 payload）
> 並解決「每卡已用」來源——**屬 P4／另案**，本節不臆造。

#### 7.18.3 回應與新鮮度

| 欄位 | 語意 |
|---|---|
| `asOf` | 投影已套用事件之最新時間（`MAX(limit_holds.updated_at)`） |
| `lagSeconds` | 當下 − `asOf`；供前端顯示新鮮度與告警 |
| `holdAttribution` | `FULL`＝所有未釋放佔用皆帶載體；`PARTIAL`＝存在無載體之歷史佔用（`V10` 不得回填）→ 各卡佔用**可能低估** |
| `instruments[]` | **僅含可歸屬載體**之佔用；無載體者**不臆造分配** |

★ **查無資料回 `404`**（`CC-PLT-4040`）——**不以「0 筆、asOf=now」冒充**已同步：
那會讓「尚未投影」看起來像「已同步且無佔用」。

#### 7.18.4 落後告警

指標 **`cardcore.limit.projection.lag.seconds`**（`ProjectionLagMonitor`，每分鐘量測）：

* 值＝全域投影最新事件時間與當下之差（秒）。
* ★ **無資料時為 `-1`**（不是 0）——回 0 會讓維運誤以為「零落後」。
  告警規則須同時涵蓋「`lag > 閾值`」與「`lag == -1` 且預期應有資料」。
* 量測失敗時**維持上次值**並 WARN（**不設 0**，避免假健康）。

#### 7.18.5 驗收（真機）

| # | 驗證 | 結果 |
|---|---|---|
| ① | 每卡彙總＝手算（含部分釋放之 `hold_amount − released_amount`） | ✅ |
| ② | 無載體佔用**不分配**給任何卡，且 `holdAttribution=PARTIAL` | ✅ |
| ③ | 查無資料 → `404`（不冒充已同步） | ✅ |
| ④ | 多幣別不混算 | ✅ |
| ⑤ | `lagSeconds` 由 `asOf` 與時鐘計算（可預測） | ✅ |
| ⑥ | 事件帶載體 → 投影落地；**重放不帶載體不得清空既有值**（`NVL` 保護） | ✅ |

## 8. 外部依賴

| 依賴 | 用途 | 失敗影響 |
|---|---|---|
| `card-service` (8082) | 卡片狀態 | 降級讀快取 |
| `limit-service` (8083) | 額度查詢／佔用 | 降級本地代佔 + 事後上送 |

| Oracle `CC_AUTH` | 授權持久化 | 服務不可用 |

---

## 9. 測試案例

| 編號 | 情境 | 結果 |
|---|---|---|
| UT-AUTH-01 | 正常卡、額度充足 | `APPROVED`/00 |
| UT-AUTH-02 | 額度不足 | `DECLINED`/51（`LIMIT`） |
| UT-AUTH-03 | 卡片已鎖卡 | `DECLINED`/51（`CARD_STATUS`） |
| UT-AUTH-04 | 查無卡片 | `DECLINED` |
| UT-AUTH-05 | 卡片逾期 | `DECLINED`（`CARD_EXPIRY`） |
| UT-AUTH-06 | 不支援幣別 | `DECLINED`（`CURRENCY`） |
| UT-AUTH-07 | MCC 格式不符 | `DECLINED`（`MCC`） |
| UT-AUTH-08 | 命中人工複核門檻（金額 ≥ 門檻） | `REFERRAL`/01，**仍佔額**（`holdAmount` 非零、有 `holdId`） |
| UT-AUTH-09 | 人工複核門檻未達 | `MANUAL_REVIEW` PASS → `APPROVED` |
| UT-AUTH-10 | 人工複核政策未啟用（`enabled=false`） | **不轉人工**（fail-safe，SM-D5） |
| UT-AUTH-11 | 門檻邊界（＝門檻即命中）／MCC 命中／MCC 為 `null`／門檻為 0 | 見 `ManualReviewPolicyTest`（5 例，含 `null` 不 NPE） |
| **UT-AUTH-12** | ★ **本地代佔之 `REFERRAL`** | `standIn=true`，**且進入待上送清單**（`§7.7.1` 迴歸） |
| **UT-AUTH-13** | ★ **遠端佔用之 `REFERRAL`** | `standIn=false`，**不**入待上送（`§7.7.1` 迴歸） |
| UT-AUTH-14 | 核准且本地代佔 | `STANDIN` 決策、`standIn=true`（既有行為不變） |
| UT-AUTH-15 | 拒絕件（`DECLINED`） | `standIn` 恆 `false`（無佔用即無須後送） |
| UT-AUTH-16 | 核決：`REFERRAL`＋`APPROVE` | `APPROVED`，**保留原佔額**（不呼叫 `release`） |
| UT-AUTH-17 | 核決：`REFERRAL`＋`DECLINE` | `DECLINED`，**釋放原佔額**（呼叫 `LimitPort.release`） |
| UT-AUTH-18 | 核決守門：非轉人工件（含 `force=true`） | `CC-AUTH-3001`（**強制授權不得繞過風控**） |
| UT-AUTH-19 | 核決守門：已核決（終態） | `CC-AUTH-3002` |
| UT-AUTH-20 | 核決守門：查無授權／缺 `operatorId`／缺 `reason`／非法 `decision` | `CC-AUTH-4040`／`CC-AUTH-3001` |
| UT-AUTH-21 | 後送：Stand-in 件上送成功 | 標記 `standin_upload_at`、回報上送時間 |
| UT-AUTH-22 | 後送：重複後送 | **冪等略過**，不產生第二次上送 |
| UT-AUTH-23 | 後送：非 Stand-in 件（無後送目標） | 拋出，**不得上送** |
| UT-AUTH-24 | 後送：上送失敗 | **不標記**（保留待上送），例外外拋供排程續試 |
| UT-AUTH-25 | 批次上送遇失敗 | **中斷本批**，成功者已標記、其餘留待下次 |
| E2E-AUTH-01 | 鎖卡後授權即時拒絕 | `DECLINED` ✅ |
| E2E-AUTH-02 | 額度服務停止 → Stand-in → 恢復 → 上送 | `STANDIN` → `standin_upload_at` 寫入 ✅ |
| E2E-AUTH-03 | **未帶 token** 呼叫 API（SSO 啟用時） | **`401`**（fail-closed）✅ |
| E2E-AUTH-04 | 超門檻授權（25,000 分／門檻 20,000 分） | **`REFERRAL`**／`MANUAL_REVIEW` FAIL-01；`LIMIT_HOLDS` 為 **`HELD`**（仍佔額）✅ |
| E2E-AUTH-05 | 對 `REFERRAL` 核決 `APPROVE` | `APPROVED`；佔用仍 `HELD`；`auth_overrides` 留痕（`operatorId`／`reason` 完整）✅ |
| E2E-AUTH-06 | 對 `REFERRAL` 核決 `DECLINE` | `DECLINED`；佔用 **`RELEASED`**（`released_amount`＝原額）✅ |
| E2E-AUTH-07 | 核決守門：非轉人工／已核決／`force=true` | `CC-AUTH-3001`／`CC-AUTH-3002`／`CC-AUTH-3001` ✅ |
| E2E-AUTH-08 | 後送：非 Stand-in 件 | `CC-AUTH-3001`（409）✅ |
| E2E-AUTH-09 | 後送：額度服務不可用（Feign 401，`GOV-006 §4 #21`） | `500 CC-PLT-5000`；**保留待上送**（`standin_upload_at` 仍空）✅ |
| **E2E-AUTH-10** | ★ **降級之 `REFERRAL` 之 `standin_flag`**（`§7.7.1` 修正驗證） | 修正前 `0` → **修正後 `1`**，且列入待上送清單 ✅ |

---

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

1. **13 大項檢核僅實作 6 項**（`AMOUNT`/`CURRENCY`/`CARD_STATUS`/`CARD_EXPIRY`/`MCC`/`LIMIT`）；其餘 7 項（EMV／CVV／風控／日限／地域／頻率／黑名單／爭議）缺資料來源，標 `isImplemented=false`，**不產生假的通過結果**。

2. **檢核順序未依契約完整排序**（契約規範 13 大項順序）。

3. **`GET /authorizations`（清單）與明細未登錄契約**（明細鍵為 `authNo` vs 契約 `{authId}`）。

4. **上送冪等鍵未做**：目前靠「成功才寫 `standin_upload_at`」；若 limit-service 收到重複 `authorizationId` 無保護。

5. **監控未接**（`CSP-004` 僅追蹤碼，Prometheus／Grafana 未串）。

6. **`authorization.authorization.approved.v1` 僅有生產端**（`§7.14`，`#A-121`）：**消費端**（`posting`／`limit`／`notification`，`SAD-004 §5.2`）**尚未實作**；**拒絕事件** `authorization.authorization.declined.v1`（`SDD-006 §4.2`）**亦尚未發射**。

7. **`sequenceNo` 為同 JVM 內遞增之替代序號**（`§7.14`）：多實例部署可能重複；正式應改為聚合層級之持久化序號。

8. ⚠️ **`REFERRAL`（轉人工）件不發射任何事件——但其已佔用額度**（`§7.14`，**實測確認**）：`AuthDecision.isApproved()` 之定義為 `{APPROVED, STANDIN}`，**不含** `REFERRAL`（該值由 `CR-022` 後加）。故轉人工件之**佔用**下游（`limit`／`posting`）**無從得知**——與 `§7.7.1` 同型之「**靜默落差**」風險。**待裁定**：應 (a) 另立 `authorization.authorization.referred.v1`，或 (b) 讓 `approved.v1` 涵蓋 `REFERRAL`（並以 `standinFlag` 區分）。**本次不擅自擇一**。

9. ✅ ~~**`limit_holds` 本地投影無餵養者**~~ → **已解除（2026-10-01，`§7.15`，`#A-128`）**：現由 `limit.hold.created.v1`／`.released` 餵養；**正常路徑**（遠端佔用）亦會寫入本地投影。

10. ~~**stand-in 情境下「本地 `holdId` 與權威 `holdId` 不一致」**~~ → ✅ **已裁定並實作（BEAM PMO 2026-10-01；`#A-129`）**：
    上送成功後**回寫** `authorizations.hold_id` 為權威 `holdId`（`§7.7`），並由 `§7.15` 之消費端
    **同步改本地列之 `id`** → 三者一致（`authorizations.hold_id` ＝ 本地列 `id` ＝ 權威 `holdId`）。
    ⚠️ **未竟**：**既有歷史資料**（本次裁定前已上送者）之 `hold_id` 仍為本地 UUID，**需一次性回填**
    （**未做**，見 §10 #12）。

11. **`limit.hold.created.v1` payload 之 `currency` 為可選欄位**（`§7.15`，`#A-128`）：原 payload **未帶**，惟 `limit_holds.currency` 為 `NOT NULL` → 消費端以「事件帶者優先，未帶則回查 `account_limit_caches`（恰一列）」處理。已列 `CR-027 §5 #14` **待裁定**（與 #10 `posted.v1` 缺 `currency` **同型**）。

12. **既有 stand-in 歷史資料之 `hold_id` 未回填**（`§7.7`，`#A-129`）：本次裁定（回寫權威 `holdId`）**僅對新上送生效**；**裁定前已上送**之授權，其 `hold_id` 仍為本地 UUID → 該批之權威 hold **仍不會被 `release` 釋放**（**既有落差**）。**待辦**：一次性回填腳本（需以 `authorizationId` 對照權威端 `limit_holds`）。**本次未做**（**不臆造對照規則**）。

---


**`CR-030` 架構裁定執行後之結案（2026-10-01）**

> ✅ **原 §10 #8（`REFERRAL` 不發射事件）已結案**——`CR-030 §2 #8` 裁定：
> `approved.v1` 之語意重新界定為「**授權已佔用額度**」，發射條件由 `isApproved()` 改為 **`holdsLimit()`**
> （`APPROVED`／`STANDIN`／**`REFERRAL`**），並補 `decision`（可選）欄位。
> **不另立 `authorization.authorization.referred.v1`**——理由：事件名 `approved` 之語意界定為
> 「**佔用成立**」（拒絕不佔用），`REFERRAL` 屬之；另立事件會使下游多接一條而語意重疊。

> ⛔ **§10 新增 #11（刻意之設計，非缺口）**：**`DECLINED` 不發射事件**（`CR-030 §2 #7`）——
> 拒絕**不改變下游狀態**（不佔額、不產待請款），依「事件＝已發生的狀態變更」原則不應灌入事件流；
> 其統計改以 **metrics**（`cardcore_authorization_declined_total{reason}`）涵蓋。
> `SDD-006 §4.2` 之 `declined.v1` payload **保留**，供日後「拒絕率即時看板」需求時啟用（無契約破壞）。

## 11. 修訂記錄

| 版本 | 說明 |
|---|---|
| 1.16 | **`CR-043 §2.1／§2.2`（2026-10-15，`#A-187` ⑥）：reversals／overrides 之回應型別修正**——原契約**誤宣告 `AuthorizeResponse`**（疑為三端點同範本之複製貼上；沖正不應回 `decision`／`checkResults`、核決有自身結果語意）。改為 **`ReversalResult`**（`authNo`／`type`〔`FULL`／`PARTIAL`〕／`amountMinor`／`holdReleased`／`reversedAt`）與 **`OverrideResult`**（`authNo`／`decision`〔`APPROVE`／`DECLINE`〕／`force`／`holdReleased`／`overriddenAt`）——皆依實作欄位（`ReversalService.ReversalResult`／`OverrideService.OverrideResult`）。★ **改 `gen_openapi.py`**（新增兩 schema ＋ 兩端點回應 `$ref`）→ 重生 → `verify_contract_dto` **不符 0 處**。§6 API 表更新；§7.6／§7.8.2 補回應型別記 |
| 1.15 | **`CR-043 §2.3`（2026-10-15，`#A-187` ⑥）：新增 §7.17 授權請求之特店名稱／輸入模式／EMV 資料之接收**——契約 `AuthorizeRequest` 宣告 `merchantName`／`entryMode`／`emvData` 三欄，實作 DTO **無此三欄** → 消費端依契約送此三欄被**靜默忽略**。BEAM PMO 2026-10-15 裁定「**請補實作接收**」。**實作**：`AuthorizeRequest`（＋`@Size`／`@Pattern` enum）→ `AuthorizeCommand`（補 `merchantName`／`emvData`；`entryMode` 原已存在惟控制器傳 `null`）→ `Authorization` 聚合（＋3 欄）→ `JdbcAuthorizationRepository`（INSERT 綁 `merchant_name`／`entry_mode`／`emv_data`；`emvData` 以 Jackson 序列化經 `JSON(?)` 綁定）。★ **DDL 事實**：`merchant_name`／`entry_mode` 早在 `V1__baseline.sql` 卻**從未被綁定**（恆 NULL）；`emv_data` 由 **`V10__authorization_entry_emv.sql`** 新增（Oracle 23ai `JSON`，比照 `JdbcPartyRepository.FLAGS`）。★ **回應不擴充**（契約未宣告 `AuthorizeResponse` 含此三欄）＝接收＝**落庫可稽核**，非「回吐」。★ 併記 **`CR-044 ③`**（§7.6／§7.15 之**權威鏡射**——部分沖正維持 `HELD`，讀模型不得寫 `PARTIALLY_RELEASED`）。**測試**：`AuthorizationRepositoryIntegrationTest` 新增**直查 DB** 之三欄往返測試（防假綠） |
| 1.14 | **`CR-035 §2.2`（2026-10-02，`#A-154`）：新增 §7.16 額度池選擇（POC-8 之 8-3／8-4／8-5）**——本服務原僅查卡人額度，專用額度**完全不參與**，致 `BR-04-01`（不佔卡人額度）與 8-5（超額拒絕）皆不成立。**新增**：`SpecialLimitPort`（`RemoteSpecialLimitPort` ＋ `StandInSpecialLimitPort`）；`AuthorizeUseCase` 之**三段式池選擇**（`checkLimit` 改收「專用額度適用性」）；`LimitPort#hold` 增 `specialLimitId`（`RemoteLimitPort`／`JdbcLimitPort`／`StandInLimitPort` 同步）。★ **`BR-04-01` 為結構性成立**（專用佔用只寫 `limit_holds.special_limit_id` ＋ `special_limits.used_amount`，**完全不動 `account_limits`**）。★ **降級為安全降級**（`limit-service` 不可用 → 回 `empty` → 退回卡人額度；**不建本地投影**，避免兩份定義漂移）；★ **Stand-in 期間專用池佔用 fail-closed**（`JdbcLimitPort` 拋 `CC-LIM-1003`，不以卡人額度代佔——否則違反 `BR-04-01` 且雙重佔用）。⚠️ **需求語意之裁定**（8-4「未設定」vs 8-5「&gt; 額度」）：採**三段式含回退**，理由為「設定專用額度不得使交易能力下降」——已明載為**解釋**而非隱含假設。**測試**：65 測 0 失敗（新增 5） |
| 1.12 | **`GOV-006` 4.8 W-2 推廣・`CR-027` 批次 B：§7.15 佔用事件消費（2026-10-01，`#A-128`）**——新增 **`limit.hold.created.v1`／`limit.hold.released.v1` → 本地 `limit_holds`** 之消費端規格（`V2__read_model.sql` 明文載明卻**從未有人餵養**之**第三個**讀模型缺口）。**實作**：`LimitHoldProjectionPort`／`LimitHoldProjectionService`／`JdbcLimitHoldProjectionRepository`／`LimitHoldListener`。★★ **以 `authorizationId` 去重**——阻斷「stand-in 後送 → 同一筆佔用兩列 → `SUM` 重複扣減 → 可用額度被低估」之**關鍵風險**（真機以**負樣本**驗證：停 limit-service 觸發 stand-in → 重啟上送 → 本地**仍 1 列**）。★ 亂序判準置於 `consumed_events`（`limit_holds` 為**業務表**，不增技術欄位）。★ **併修正 `JdbcLimitPort.availableLimit`** 之 status 過濾缺陷（原僅計 `HELD`，漏計 `PARTIALLY_RELEASED`；實測 86,000 vs 96,000）。**併補** §10 #9～#11 |
| 1.13 | 2026-10-01 | WorkBuddy AI | **`CR-030` 架構裁定（`#A-142`）**：①**§7.14 第 2 點改寫**——發射條件由 `AuthDecision.isApproved()` 改為 **`Authorization.holdsLimit()`**（含 **`REFERRAL`**）；**本事件之語意重新界定為「授權已佔用額度」**；併載「原條件造成**額度被佔用而下游不知**之真缺陷」。②**§7.14 新增第 3 點**：payload 補 **`decision`（可選）**。③**§10 #8 結案**；**新增 #11**（`DECLINED` **刻意不發射**，改以 metrics 涵蓋）。★ 併更正 `AuthorizeUseCase` 原註解之不實（原註「`REFERRAL` 屬核准路徑（與 `isApproved()` 定義一致）」——**與程式碼矛盾**） |
| 1.11 | **`GOV-006` 4.8 W-2 推廣・事件 #3：§7.14 授權核准事件輸出（2026-10-01，`#A-121`）**——新增 **`authorization.authorization.approved.v1`** 之**生產端**規格（`SDD-003` 時序圖與 `SDD-006 §4.2` **均已載明，惟從未有人發射**）。**主題** `cc.authorization.authorization.approved`；**同交易寫 Outbox** ＋ relay；**僅「已核准」路徑發射**（`REFERRAL` 轉人工亦屬核准路徑——**已佔用額度**）。**實作**：`V9__outbox.sql` ＋ `OutboxPort`／`AuthEventFactory`／`JdbcOutboxRepository`／`KafkaEventPublisher`／`OutboxRelayConfig`／`OutboxRelayScheduler`。**併補** §10 #6／#7 之誠實揭露（消費端未實作、拒絕事件未發射、序號簡化） |
| 1.10 | **`GOV-006` 4.8 W-2 推廣・事件 #2：§7.13 額度事件消費（2026-10-01）**——新增 **`limit.limit.adjusted.v1` → `account_limit_caches`** 之消費端規格（`SAD-004 §5.2` 載明卻從未實作；`V2__read_model.sql` 之投影**無餵養者**）。**實作**：`AccountLimitProjectionService`／`AccountLimitProjectionPort`／`JdbcAccountLimitProjectionRepository`（冪等＋亂序＋upsert **同交易**，**只覆寫 `total_amount`**）／`AccountLimitChangedListener`（`@KafkaListener`）；遷移 **`V8__limit_consumption.sql`**（`account_limit_caches.last_sequence_no`，共用 `consumed_events`）。**併處理命名落差**（`V2` 之 `limit.limit.changed.v1` → 依 `SAD-004 §5.2` 之 `limit.limit.adjusted.v1`）。真機驗證通過 |
| 1.9 | **`GOV-006` 4.8 W-2 第一切片：§7.12 卡片狀態事件消費（2026-10-01）**——新增 **`card.card.status-changed.v1` → `card_status_caches`** 之消費端規格。**緣由**：`V2__read_model.sql` 明文載明該投影，惟**從未有人餵養**（`JdbcCardPort` 讀之、無寫入者 → 讀模型恆空）。**實作**：`CardStatusProjectionService`／`CardStatusProjectionPort`／`JdbcCardStatusProjectionRepository`（冪等＋亂序＋upsert **同交易**）／`CardStatusChangedListener`（`@KafkaListener`）；遷移 **`V7__card_status_consumption.sql`**（`consumed_events` ＋ `card_status_caches.last_sequence_no`）。**真機**：BEFORE count=0 → 發布事件 → upsert 成功；**冪等**（重送同 `eventId` → version 不變）／**亂序**（送舊 seq → **不覆寫**）皆通過。⚠️ **生產端（card-service 發射）未實作**（下一片） |
| 1.8 | **桶 C 解鎖（`#A-114`）：§7.11 交易狀態機檢視（2026-10-01）**——新增 **`GET /api/v1/authorizations/{authNo}/state`**（`AuthorizationStateView`），解鎖 `SDD-007 §5.1.2.3` 桶 C「**交易狀態機與冪等**」（原記「均無端點」）。**語意**：對單一授權呈現**狀態機視角**——當前狀態（`decision`）、佔用（`limit_holds`，以 `hold_id` 關聯）、**狀態轉移歷程**（由 `created_at`／`override_at`／`reversed_at` 權威時間欄推導）。**設計要點**：**(i)** 狀態機視角 vs `§7.9` 事件時間軸——**互補不重複**；**(ii)** **不以 `decision` 推導「是否曾核決／沖正」**（改權威時間欄，避 `§7.7.1` 同型缺陷）；**(iii)** **冪等（Redis）如實揭露、不重現**（不臆造欄位）。契約重生 0 錯誤 |
| 1.7 | **`CR-026` 新增統計端點 `GET /api/v1/authorizations/stats`（2026-10-01）**——新增 **§7.10** 授權統計（供 `/dashboard` 業務 KPI）；`pendingUpload`＝`standin_flag=1 AND standin_upload_at IS NULL`（依 `V4__standin_upload.sql`）；`today`＝當日授權數；`byDecision` 取自 DDL CHECK 之 4 值。契約重生 0 錯誤；`:services:authorization-service:test` BUILD SUCCESSFUL（3 個測試 fake 補 `stats()`） |
| 1.6 | **`CR-025` 稽核軌跡查詢端點（2026-09-29）**——新增 **§7.9 `GET /authorizations/audits`**，解鎖 `SDD-007 §5.1.2.3` 桶 C「稽核軌跡」（原記「**表存在但無查詢端點**」——資料寫得進去、**讀不出來**）。**設計三要點**：**(i) 統一時間軸**（`auth_overrides` 與 `auth_reversals` 兩表 `UNION ALL` 後依時間遞減——使用者關心「這筆授權發生過什麼」而非「屬於哪張表」）；**(ii) `action` 值域正規化**（`reversal_type \|\| '_REVERSAL'`）；**(iii) `actor` 對沖正如實回 `null`**（`auth_reversals` **無操作者欄位**——**不臆造**）。**append-only 唯讀**（可竄改即失去證據力）。契約重生 **79 端點、0 錯誤**。**真機 3 項**：`ALL` 4 筆正確排序／`OVERRIDE`／`REVERSAL` 各 2 筆／null 欄位如實 |
| 1.5 | **`CR-023` 後送作業台之端點補齊（2026-09-24）**——**§7.7 之「職責」表補明端點對應**：原表僅列三項職責而**未對應端點**，實作**僅有單筆**之 `POST …/{authNo}/advices` → 「**查詢待補送佇列**」與「**批次手動上送**」**無端點可達**，致 `SDD-007 §5.4.23`（`/auth/stip` 頁）**無法完整實作**。新增 `GET /authorizations/standin?status=PENDING\|UPLOADED\|ALL` 與 `POST /authorizations/standin/advices`；**併明訂**清單條件為 `standin_flag = 1`、**不得**以決策碼篩選（`§7.7.1`——否則**轉人工件被漏**）。契約重生 **77 端點、驗證 0 錯誤** |
| 1.4 | **`CR-022` 真機驗證後補記（2026-09-24）**：新增 **§7.7.1 `standin_flag` 之判定依據**——明訂須依「**佔用方式**」（`HoldResult.standIn`）而非決策碼；**併修實作**（`Authorization` 聚合保留 `standIn` 旗標、`JdbcAuthorizationRepository` 改用之）。**緣起**：真機實測發現 `§7.8` 新增 `REFERRAL` 後，「`decision=STANDIN`」與「`standIn=true`」**不再等價**，轉人工件在額度服務不可用時**漏標 `standin_flag`** → **該筆永不後送**（額度權威側永久落差＝**超額風險**）。**§9 補 24 例單元測試與 7 項真機實測** |
| 1.3 | **`CR-022` 裁定後之實作規格（2026-09-24）**：**§7.7 改寫為 (A) 後送授權通知**（BEAM PMO 裁定；含與排程上送之分工、冪等守門、契約修訂定案）；**新增 §7.8 人工授權／強制授權**（RFP `FR 1.1` 第 4 項明文要求；含參數化轉人工門檻 `CheckCode.MANUAL_REVIEW`、狀態守門 SM-D1~D5、僅限對 `REFERRAL` 核決）。併重生 17 份契約、新增遷移 `V6__auth_overrides.sql` |
| 1.2 | **`CR-021` 真機驗證後補記**：**§7.6 雙軌釋放**——`StandInLimitPort.release` 與 `hold` **對稱**（遠端優先、失敗降級本地並 WARN、遠端成功後 best-effort 清本地同列）；**FULL 沖正已真機驗證**（`AUTH000035`）。⚠️ 併發現既有缺陷：`LimitServiceClient`（Feign）呼叫 limit-service 回 **401**（未接 M2M outbound token）→ 授權一律降級 Stand-in（已登 `GOV-006 §4 #21`） |
| 1.1 | **`CR-021` 補實作之規格先行**：§6 補 `check-results`／`reversals` 兩端點（**契約此前宣告而未實作**）＋ `advices` 標記「語意待裁定、本期未實作」；新增 **§7.5 檢核明細查詢**（查落庫、不重算）、**§7.6 沖正**、**§7.7 advices 兩種語意之裁定建議** |
| 1.0 | 初版：回溯補建，與既有程式一致 |
