# CR-054　卡片級額度投影（`CR-052` C-2）**前置盤點與三重缺口**

| 項目 | 內容 |
|---|---|
| 文件編號 | CR-054 |
| 版本 | **1.2** |
| 狀態 | **P1／P2／P3／P4 全數完成**（2026-10-17；P4 依 BEAM PMO 選定 **B-1 全卡共用**＝**不做人為分攤**，見 §12） |
| 觸發 | BEAM PMO 2026-10-17「先做 C-2 投影的前置盤點」 |
| 需求依據 | `CR-052 §4` C-2＝**(b) 事件投影**；`CR-052 §4.1` 雙軌邊界（投影僅供查詢、授權走即時） |
| 影響模組 | `limit-service`（事件 payload）、`authorization-service`（投影表）、`SDD-006 §4.2`（契約）、`SAD-004 §5.2`（訂閱） |
| 關聯 | `CR-052`（語意）／`CR-053`（維度模型，已落地 `V11`／`V12`）／`CR-028`（hold 事件權威化）／`CR-031 C-5`（投影先例） |

---

## 1. 盤點結論（先講結果）

| # | 結論 | 證據 |
|---|---|---|
| **①** | **C-2 的「共享池分攤」前提已被 C-1(ii) 改變**——卡片有了獨立主額度後，「分攤」只存在於**仍走共享池（ACCOUNT 維度）**的商品；哪些商品走共享池屬 **D-2 參數化維度**（未做、待客戶口徑）→ **分攤規則目前無所依附** | `CR-052 §4.2` C-1 改 (ii)；`CR-053 §5.1` D-2 未達 |
| **②** | **per-card 佔用的資料覆蓋率＝0%**——實查 34 筆佔用**全部**無 `instrument_id`（其中 29 筆仍 `HELD`）；且 `V10` 明示**不得回填** | 真 Oracle 實查（見 §3.3） |
| **③** | **即使新交易帶了載體，事件也帶不出去**——`limit.hold.created.v1`／`released.v1` 之 payload **無 `instrumentId`**（`SDD-006 §4.2` 權威定義即無此欄）；程式之 `hold()` 收到 `instrumentId` 但**未傳入**事件工廠 | `SDD-006 §4.2`＋程式實查（見 §3.2） |

**→ 綜合判定**：C-2 投影要能產出「每卡可用額度」，須同時補齊**三處**（資料／事件／投影表），
而其中「分攤規則」還依賴 **D-2**（客戶口徑）。**現在開工＝必然產出空投影或臆造數字**。

---

## 2. 裁定原文與邊界（複誦，避免走偏）

`CR-052 §4` C-2：**(b) 事件投影**（推翻「即時彙算」建議）；`§4.1` BEAM PMO**確認＝是**之雙軌邊界：

| 要求 | 含義 |
|---|---|
| 投影**僅供查詢**（客服／報表／App 人讀） | 不得供授權使用 |
| **授權走權威即時**（`account_limits`＋`special_limits`＋`limit_holds`） | 額度正確性為風控底線；寧可查詢慢 |
| 投影帶 **`as_of`** 並於 API 揭露 | 讓消費端知資料新鮮度 |
| **落後告警** | 積壓／延遲可觀測 |
| **冪等 upsert ＋ 亂序防護 ＋ 可事件重放重建** | 比照 `CR-031 C-5` 先例 |
| **回溯缺口揭露** | `V10`：既有佔用無 `instrument_id`、不得回填 |

---

## 3. 現況實查（皆為本機真環境）

### 3.1 事件與消費者

| 事件 | 生產者 | 消費者 | payload 是否含載體 |
|---|---|---|---|
| `limit.hold.created.v1` | `limit-service` | `authorization-service`（本地 `limit_holds` 投影） | ❌ **無** `instrumentId` |
| `limit.hold.released.v1` | `limit-service` | 同上 | ❌ **無** |
| `limit.limit.adjusted.v1` | `limit-service` | `authorization-service`（`account_limit_caches`） | ❌ 無（且**帳戶維度**語意，卡片級已刻意不發，見 `SCS-002 §7.16.3`） |

★ **`SDD-006 §4.2` 權威 payload**：`limit.hold.created.v1` = `holdId`, `accountId`, `holdAmount`,
`holdType`, `authorizationId`＊, `currency`＊——**確實無 `instrumentId`**。

★ **程式實查**（`LimitApplicationService`）：`hold(...)` 之簽名**有** `instrumentId`
（並已寫入權威表 `limit_holds.instrument_id`，`V10`），但呼叫
`holdCreatedEnvelope(holdId, accountId, amount, holdType, authorizationId, currency, …)`
**未傳**該值（工廠亦無此參數）→ **權威表有、事件沒有**，這正是「投影永遠拿不到」的根因。

### 3.2 既有投影機制（可複用，非從零）

| 機制 | 位置 | 可複用點 |
|---|---|---|
| `consumed_events`（`event_id` 唯一鍵） | `CC_LIM`（`V5`） | **冪等去重** |
| `MAX(sequence_no) per aggregate_id` | `CC_LIM` | **亂序防護**（不污染權威表） |
| `account_limit_caches.last_sequence_no` | `CC_AUTH`（`V8`） | 投影側亂序判準 |
| `AccountLimitProjectionService`／`AccountLimitChangedListener` | `authorization-service` | **既有消費者骨架**（消費 `limit.limit.adjusted.v1`） |
| `report-service` 之 `migration-postgres` 讀模型 | PostgreSQL `rpt` | CQRS 讀側先例 |

★ **PostgreSQL `proj` schema 目前為空**（`\dt proj.*` → 無表）——「CQRS 讀模型都放 Postgres」
之假設**不成立**；既有投影實際都在**各服務之 Oracle schema**。故 C-2 投影位置須先定（見 §5 Q4）。

### 3.3 資料覆蓋率（真 Oracle 實查）

| 指標 | 值 |
|---|---|
| `CC_LIM.LIMIT_HOLDS` 總筆數 | **34** |
| 其中 `INSTRUMENT_ID IS NULL` | **34（100%）** |
| 其中 `STATUS='HELD'` | **29** |
| `CC_LIM.ACCOUNT_LIMITS` 維度分布 | `ACCOUNT` **3**／`CARD` **0** |

**→ 現行資料下，「每卡佔用」與「每卡可用」的投影結果將**全為 0 或不可計算**。
且 `V10` 已明示**不得回填**——此缺口**不會隨時間自動消失**，只能靠**新交易帶載體**逐步累積。

---

## 4. 三重缺口（缺一不可）

```
① 權威資料：既有 34 筆佔用無 instrument_id（100%）——不得回填（V10）
        ↓
② 事件：hold.created／released payload 無 instrumentId（SDD-006 §4.2）
        ↓（即使①補齊，②不補 → 投影仍拿不到）
③ 投影表：CC_AUTH.limit_holds 無 instrument_id 欄位
        ↓（即使①②補齊，③不補 → 無處落地）
④ 分攤規則：僅「ACCOUNT 維度商品」需要；哪些商品走共享池＝D-2（未做）
```

| 缺口 | 性質 | 可否本地自主 |
|---|---|---|
| ① 歷史資料 | **不可逆**（不得回填） | — 只能揭露與等待新資料累積 |
| ② 事件 payload | **契約變更**（可選欄位、不升版，`SAD-004 §5.3` 有先例） | ✅ 可做，需 CR 承載 |
| ③ 投影表欄位 | 遷移（新增可空欄，同 `V11` 教訓：**勿直接 NOT NULL**） | ✅ 可做 |
| ④ 分攤規則 | **客戶業務口徑** | ❌ 需 D-2 與客戶裁定 |

---

## 5. 待裁定清單（Q1～Q5；每項附建議立場，供**整批**裁）

| # | 問題 | 建議立場 | 理由 |
|---|---|---|---|
| **Q1** | 是否**先補事件 `instrumentId`**（②）？ | **建議：補**（`hold.created`／`released` 各加**可選** `instrumentId`，不升版） | 這是投影的**唯一資料來源**；不補則 C-2 永不可行。`SAD-004 §5.3` 已有「新增可選欄位不升版」先例（`authorizationId`／`currency` 即如此補） |
| **Q2** | 投影**位置**？ | **建議：`authorization-service` 之 Oracle 本地投影**（與既有 `limit_holds`／`account_limit_caches` 同處），**不**新增 Postgres `proj` 表 | ① 既有機制（冪等表／亂序）可直接複用；② `proj` 目前空，另立管線成本高；③ 授權與查詢同庫便於對照（但**仍不得**以投影做授權判斷） |
| **Q3** | 投影**範圍**？ | **建議：只投影「每卡額度主檔＋每卡可歸屬佔用」**，不投影「分攤後可用」 | 分攤規則未定（Q5），先投影**原始事實**（可重放、可驗證），分攤留給查詢層或第二階段 |
| **Q4** | 歷史缺口如何處理？ | **建議：API 回 `holdAttribution=PARTIAL`**（已實作於 `SCS-002 §7.15.2`）＋文件揭露；**不回填、不估算** | 與 `V10` 一致；估算＝臆造 |
| **Q5** | 分攤規則（僅共享池商品） | **建議：暫不實作**，待 D-2 凍結「哪些商品走共享池」後另立 CR | 現在寫分攤＝為不存在的商品編規則（`CR-045` D3 同類禁令） |

---

## 6. 建議之分批（若 Q1～Q4 照建議核准）

| 批次 | 內容 | 依賴 | 可驗證性 |
|---|---|---|---|
| **P1** | 契約補 `instrumentId`（可選）＋`LimitEventFactory`／`apply` 傳值 | Q1 | 真機：新佔用事件 payload **含** `instrumentId`；舊事件相容（欄位可 null） |
| **P2** | `CC_AUTH.limit_holds` 加 `instrument_id`（可空）＋消費者寫入 | P1 | 真機：消費後投影列有載體；**亂序／重複**事件不覆蓋 |
| **P3** | 投影查詢端點（帶 `as_of`＋`holdAttribution`）＋落後告警 | P2 | 真機：投影落後時 `as_of` 如實；重放可重建 |
| **P4** | 分攤（僅共享池商品） | **D-2＋客戶口徑** | 待定 |

★ **P1 之前不動任何投影表**——先讓事件帶得出載體，否則投影必為空殼（本專案「有人發、沒人收」
與「有欄位、沒有值」兩類假綠之混合體）。

---

## 7. 明確不建議（避免重工）

* ❌ **不要**為 C-2 新建 Postgres `proj` 表（既有投影不在那裡，管線成本高且無先例支撐）。
* ❌ **不要**回填既有 34 筆佔用之 `instrument_id`（`V10` 明文禁止；回填＝臆造）。
* ❌ **不要**在分攤規則未定前實作「卡片可用＝共享池按比例」（無商品口徑即無從驗證）。
* ❌ **不要**以投影替代授權判斷（`CR-052 §4.1` 風控底線）。
* ❌ **不要**把卡片級調額事件化為 `limit.limit.adjusted.v1`（該事件語意為帳戶總額度，
  已在 `SCS-002 §7.16.3` 明確禁止）。

---

## 9. P4（分攤）所需之客戶口徑——**具體問題清單**（2026-10-17）

> P1～P3 已完成（見 §10）。P4 之所以卡住，不是技術，是**業務語意未定**。
> 以下每題都附「若客戶沒意見，我方建議值」——可直接回「照建議」，不必逐條討論。

### Q-A｜哪些商品走「共享池」、哪些走「卡片獨立」？（D-2 核心）

| # | 問題 | 我方建議（未答時之預設） |
|---|---|---|
| A-1 | 客戶的信用卡商品中，**哪些**是「歸戶／帳戶共享額度」、**哪些**是「每卡獨立額度」？ | 若無法一次給全：**預設全部走「卡片獨立」**（符合 RFP §2.4(1) 與 C-1(ii)），共享池商品日後再逐個指定 |
| A-2 | 這個區分**放在哪裡**？商品參數（每個商品一值）還是系統參數（全域預設＋例外）？ | **商品參數** `product / limit.model`，值域 `ACCOUNT`／`CARD`／`HOUSEHOLD`／`PARTY`／`CORP_SUB`；**缺值 fail-closed**（不猜） |
| A-3 | 同一張卡是否可能**同時**有「卡片主額度」與「共享池」？（例如一般消費走卡片、預借現金走共享） | **預設：不並存**（一卡一模型）。若客戶要並存，需先定義「哪些交易走哪個池」——屬新 CR |

### Q-B｜若走共享池，卡片級「可用」怎麼呈現？

| # | 選項 | 說明 | 我方建議 |
|---|---|---|---|
| B-1 | 全卡共用 | 每張卡都看到「帳戶總可用」，不分配 | ✅ **建議此項**——語意最誠實（共享就是共享），且不需分攤規則 |
| B-2 | 平均分攤 | 總可用 ÷ 卡數 | ❌ 不建議：卡數變動即跳動，且與實際消費能力不符 |
| B-3 | 依已用比例 | 按各卡已用比例分配剩餘 | ❌ 不建議：需 per-card 已用（**無權威來源**） |
| B-4 | 主卡優先／附卡受限 | 主卡先用，附卡吃剩餘 | ⚠️ 需客戶明確規則（主卡怎麼認定？剩餘怎麼算？） |

> ★ **B-3／B-4 的共同前提**是「每卡已用」——目前**無來源**（已入帳交易不帶載體、歷史佔用不得回填）。
> 若客戶選 B-3／B-4，**必須先解決 per-card 已用**（可能需 `posting` 帶 `instrumentId` 並回溯新資料）。

### Q-C｜預借現金額度（C-4 未落地之項）

| # | 問題 | 我方建議 |
|---|---|---|
| C-1 | 預借現金額度是**獨立額度**，還是「主額度的一個子上限」？ | 若客戶未答：**暫不實作**（現無資料模型，強做即臆造） |
| C-2 | 若是子上限：比例（如主額度 30%）還是固定金額？ | 建議**參數化**（商品參數 `limit.cash-advance-ratio`），缺值 fail-closed |
| C-3 | 預借現金佔用是否與一般消費**共用**同一個 `limit_holds`？ | 建議**共用**（以 `hold_type` 區分），避免兩套佔用表 |

### Q-D｜企業子額度（`CORP_SUB`）

| # | 問題 | 我方建議 |
|---|---|---|
| D-1 | 企業子額度隸屬「企業戶」還是「卡片」？ | 建議**隸屬企業戶**（`subject_type=CORP_SUB`，`subject_id=企業戶識別`） |
| D-2 | 子額度佔用是否**扣減**上層（企業總額度）？ | 建議**扣減**（否則上層會超放）——但需客戶確認「是否雙重計算」 |
| D-3 | 員工離職／卡片停用時，子額度如何回收？ | 建議**隨卡停用自動釋放**（與 `card.status-changed` 連動），細節待客戶 |

### Q-E｜投影新鮮度容忍度（影響告警閾值）

| # | 問題 | 我方建議 |
|---|---|---|
| E-1 | 客服／App 查詢可接受幾秒落後？ | 建議 **≤ 60 秒**（現行 `ProjectionLagMonitor` 每分鐘量測，閾值暫設 300 秒告警） |
| E-2 | 若超過容忍度，前端應「顯示落後警示」還是「改打權威即時」？ | 建議**顯示 `asOf` 與落後秒數**，由使用者判斷；不自動切換（避免兩套數字混淆） |

---

## 10. 執行結果（2026-10-17，`#A-288`）——P1／P2／P3 已完成

| 批次 | 內容 | 狀態 | 驗證 |
|---|---|---|---|
| **P1** | `limit.hold.created.v1`／`released.v1` payload 加**可選** `instrumentId`（`SDD-006 §4.2` 已更新；`SAD-004 §5.3` 不升版）；`released` 由同交易 `findHoldInstrumentId` 回查 | ✅ | 真機：payload **值等於**傳入載體；無載體時欄位存在且為 `null`；既有欄位不受影響 |
| **P2** | `CC_AUTH.LIMIT_HOLDS` 加 `instrument_id`（`V14`，可空）；消費者寫入；**`NVL(?, instrument_id)` 防重放清空** | ✅ | 真機：事件帶載體→投影落地；重放不帶載體**不清空**；舊事件相容為 `null`；冪等／亂序守門有效 |
| **P3** | `GET /api/v1/limit-projections/accounts/{accountId}/instruments`＋`ProjectionLagMonitor`（Gauge，無資料為 `-1`） | ✅ | 真機：每卡彙總＝手算；無載體**不分配**且 `PARTIAL`；查無回 `404`；多幣別隔離；`lagSeconds` 可預測 |
| **P4** | 分攤 | ⏸ **待 Q-A～Q-E 口徑** | — |

**★ 測試**：`limit-service` **198 測 0 失敗**（新增 4 例）、`authorization-service` **160 測 0 失敗**（新增 10 例）；
契約 **112 端點／0 錯誤**、`verify_contract_dto` 不符 0。

**★ 實作期教訓（Oracle）**：`COMMENT ON COLUMN` **不可用相鄰字串字面值**（Oracle 不像 C／Java 自動串接）
→ 實測 `ORA-03046`；已改單一字串。

**★ 仍誠實揭露**：投影**不含**每卡主額度與每卡可用額度（來源不在投影）；歷史 34 筆佔用仍無載體
（不得回填，僅能靠新交易累積）；`HOUSEHOLD`／`PARTY`／`CORP_SUB` 維度無寫入路徑。

---

## 12. P4（分攤）執行結果（2026-10-17，`#A-296`）

BEAM PMO 指示「`CR-054` 的 C-2 P4 分攤…請執行」。§9 Q-B 之裁定為 **B-1 全卡共用**，
故本批**不是**「實作分攤」，而是**把「不分攤」變成明確的 API 語意**——避免日後有人
以為「還沒做」而補上一套分攤規則。

### 12.1 落地

| 項目 | 內容 |
|---|---|
| 契約 | `LimitProjectionView` 新增 `allocationMode`（enum，目前僅 `NONE_SHARED`） |
| 語意 | `NONE_SHARED` ＝**不做人為分攤**：各卡看到的「可動用總額度」是**同一個數字**（帳戶可用），不由投影分配給各卡 |
| 不變 | `instruments[]` 仍只回**各卡未釋放佔用**；**不**新增「每卡可用額度」 |
| 程式 | `LimitProjectionQueryService.AllocationMode` 列舉＋javadoc 記錄理由；回應帶 `allocationMode` |

### 12.2 為何「不分攤」才是正確答案（而非偷懶）

| 選項 | 為何不可行 |
|---|---|
| B-2 平均分攤 | 卡數一變（加卡／停卡）每卡數字就跳動，且與實際消費能力不符 |
| B-3 依已用比例 | 需「**每卡已用**」——本專案**無權威來源**（已入帳交易不帶 `instrument_id`、歷史佔用不得回填，`CR-053` D-4）→ 硬做即臆造 |
| B-4 主卡優先 | 同上（需每卡已用）＋「主卡怎麼認定」無口徑 |
| **B-1 全卡共用** | 語意最誠實（共享就是共享）、不需分攤規則、不會產生會漂移的數字 |

### 12.3 誠實揭露

| 項 | 說明 |
|---|---|
| **投影仍不含「可用額度」** | 主額度在 `limit-service` 之 CARD 維度、已用為帳戶層且無 per-card 來源 → 可用額度**只能**向 `limit-service` 權威即時查（`CR-052 §4.1` 雙軌邊界）。本批**不**為了讓畫面好看而把帳戶可用塞進投影 |
| **`allocationMode` 目前恆為單一值** | 若未來客戶改選 B-3／B-4，**前提**是先有 per-card 已用（需 `posting` 帶 `instrument_id` 並回溯新資料）→ 屆時再擴值域，不可先加值卻無資料支撐 |
| 每卡已用 | 仍無來源（`#A-288` 已揭露，本批未改變） |

---

## 11. 修訂紀錄

| 版本 | 日期 | 內容 |
|---|---|---|
| **1.2** | 2026-10-17 | **P4 落地（`#A-296`）**：依 BEAM PMO 選定 **B-1 全卡共用** → 契約新增 `allocationMode`（`NONE_SHARED`），把「**不做人為分攤**」變成明確語意（§12）。★ 揭露：投影仍不含可用額度（權威在 `limit-service`）、`allocationMode` 目前恆為單一值（B-3／B-4 之前提 per-card 已用仍無來源） |
| 1.1 | 2026-10-17 | P1／P2／P3 完成並真機驗證（`#A-288`）：事件補 `instrumentId`、投影加 `instrument_id`（`NVL` 防重放清空）、投影查詢端點＋落後告警（無資料為 `-1`）。★ 揭露：投影不含每卡主額度與可用額度、歷史 34 筆佔用無載體 |
| 1.0 | 2026-10-17 | 開立並完成盤點：確認 **C-2 現階段不具備開工條件**——① 分攤前提已被 C-1(ii) 改變、依賴 D-2；② per-card 佔用資料覆蓋率 **0%**（34/34 無 `instrument_id`，29 筆仍 HELD）；③ 事件 payload 無 `instrumentId`（`SDD-006 §4.2` 權威＋程式實查「權威表有、事件沒有」）；④ 投影表無該欄；⑤ PostgreSQL `proj` 為空（既有投影皆在服務本地 Oracle）。列 Q1～Q5 待裁定與 P1～P4 分批建議 |
