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 分批建議 |