CR #236 » CR-035_POC8剩餘四項之設計與裁定.md
CR-035 POC-8 剩餘四項之設計與裁定——8-2 權限參數化、8-3/8-5 授權端扣抵、8-6/8-7 臨時額度串動、scope_ref
| 項目 | 內容 |
|---|---|
| 文件編號 | CR-035 |
| 版本 |
1.1(✅ Phase 1/2/3 全數完成——8-3/8-5、8-2、8-6/8-7 皆已實作並真機驗證。★ 更正 §2.4 之「繳款截止日」來源:原載讀 CC_STMT 為跨 schema、不可行,已改為推算並標記 DERIVED。★ §2.3 之折衷已處理:角色改由已驗證之權杖決定(折衷移除);客層保留但附四項證據。前版 1.0) |
| 狀態 | ✅ 已裁定並全數執行完畢(Phase 1/2/3 皆已實作並真機驗證) |
| 提出日 | 2026-10-02 |
| 提出者 | WorkBuddy AI(開發團隊) |
| 緣起 | BEAM PMO:「請接續完成 8-2 角色/客層上限、8-3/8-5 授權端扣抵與超額拒絕(屬 authorization-service,在此之前專用額度尚不能實際動用)及 8-6/8-7 臨時額度串動、scope_ref(MCC)」 |
| 前案 |
CR-014 v1.1(#A-153)——專用額度資料模型已落地;本 CR 為其 §8 已知限制之收斂 |
| 關聯 |
BRD-004 POC-8、BRD-001 §5.4 BF-04(BR-04-01~BR-04-04)、SDD-001 §3.4、SDD-005、SDD-006、SCS-001、SCS-002、GOV-006 2.2.3b
|
| 嚴重度 | 🟡 中——在此之前專用額度「可設定但不能動用」(POC-8 之核心價值未兌現) |
1. 動工前事實查證(真機;cardcore-preflight-fact-check)
| 查證項 | 結果 |
|---|---|
CC_LIM.SPECIAL_LIMITS 欄位 |
12 欄(含 currency);無 scope_ref → 需新增 |
CC_LIM.LIMIT_HOLDS 欄位 |
11 欄;無 special_limit_id → 需新增。hold_type IN ('GENERAL','SPECIAL','INSTALLMENT')——SPECIAL 值早已在值域內(既有設計已預留,惟從未使用) |
| 「客層」來源 | ★ 全庫無 SEGMENT 欄位。最接近者為 CC_PRODUCT.PRODUCTS.CARD_GRADE(卡等)與 CC_RW.POINTS_TIERS.TIER_CODE(忠誠層級) |
| 「角色」來源 | ★ 不存在——LimitAdjustmentCommand 無角色欄位;平台亦無 RBAC 資料源 |
posting.transaction.posted.v1 payload |
postingId/txNo/accountId/txType/amount/currency/postingDate/subAccountEntries[]——★ 不含 authorizationId(8-6 需要它才能識別「哪一筆用了專用額度」)。惟 CC_POSTING.TRANSACTIONS 有 AUTHORIZATION_ID 欄位 → 可作為可選欄位補入(SAD-004 §5.3 不升版) |
| 繳款截止日來源 |
CC_STMT.STATEMENTS.payment_due_date(NOT NULL)、CC_POSTING.BALANCES.payment_due_date(可空);StatementGenerationService 以 帳單日 + 25 天 產生 |
| 授權服務之額度讀模型 |
account_limit_caches(total_amount/used_amount)+ limit_holds——無任何專用額度欄位
|
| ACL |
limit-service 接受 cc-authorization-service ✓;product-service 接受 cc-limit-service ✓;★ card-service 只接受 cc-authorization-service → limit-service 不得呼叫 card-service(影響 8-2 之客層自動取得) |
2. 逐項設計與裁定
2.1 scope_ref(用途範圍,MCC)——POC-8 之 8-3 前置
裁定:SPECIAL_LIMITS 新增 scope_ref VARCHAR2(64 CHAR)(V7)。
| 值 | 語意 |
|---|---|
NULL/空白 |
不限定——該用途之任何 MCC 皆可動用 |
| 非空 |
逗號分隔之 MCC 白名單(如 9311,9399);交易之 MCC 須恰在其中
|
理由:BRD-001 §5.4 BF-04 之措辭為「依用途設定(繳稅/保費/特定 MCC)」——
用途為主軸、MCC 為細化。故「不限定」是合法設定,非缺漏。
多筆命中時之選擇順序(決定性,避免不確定行為):
-
scope_ref非空且命中 者優先(較特定); - 其次為
scope_ref為NULL(不限定)者; - 同級則依
purpose升冪、id升冪(保證決定性)。
⚠️ 繳稅之 MCC 慣例:
9311(Tax Payments)/9399(Government Services, NEC)。
種子資料將以scope_ref='9311,9399'設定 TAX 專用額度。
2.2 8-3/8-5:授權端扣抵與超額拒絕(本 CR 之核心)
現況:AuthorizeUseCase 只查 limitPort.availableLimit(accountId, currency)(卡人額度),
專用額度完全不參與——故 8-3 之「不佔卡人額度」與 8-5 之「超額拒絕」皆無法成立。
裁定:三段式池選擇(Pool Selection),於 AuthorizeUseCase 內實作:
① 查「適用之專用額度」(account, currency, mcc)→ 得 (specialLimitId, 可用專用額度)
② checkLimit:
若 ① 存在 且 可用專用額度 ≥ 交易金額 → PASS(pool = SPECIAL)
否則若 卡人可用額度 ≥ 交易金額 → PASS(pool = CARDHOLDER,8-4)
否則 → FAIL(CC-LIM-1001,8-5)
③ hold:pool = SPECIAL → holdType='SPECIAL' + specialLimitId
pool = CARDHOLDER → holdType='GENERAL'
★ 關鍵裁定(避免「雙重佔用」):使用專用額度時,LIMIT_HOLDS 只記一筆(hold_type='SPECIAL'),
且不動** account_limits 之任何欄位**。故「不佔卡人額度」(BR-04-01)是結構性成立,
不是靠事後沖銷——下游之 available = total − used − holds 天然不含該筆(見 SCS-001 §7.15 之既有設計)。
專用額度池之佔用:special_limits.used_amount 於 hold 時 +=、release 時 -=(下限 0,同一交易)。
→ 與卡人額度之 used_amount+limit_holds 雙層語意刻意不同:
專用額度之「已用」=佔用中(授權已佔、尚未釋放),
因專用額度無「請款比對」之獨立訊號可依(見 §3 已知限制)。
Stand-in 降級:limit-service 不可用時,StandInLimitPort 之本地讀模型沒有專用額度資料
→ 查無適用之專用額度 → 自然退回卡人額度(8-4)。此為安全降級(不會超額放行專用池)。
⚠️ 如實揭露:Stand-in 期間專用額度不可用(非「假裝有」)。
新增端點(limit-service):
GET /api/v1/accounts/{accountId}/special-limits/applicable?currency=&mcc=&amount=
→ 回 SpecialLimitApplicabilityView { applicable, specialLimitId, purpose, scopeRef, availableAmountMinor, reason }
(applicable=false 時亦回 200——「無適用」是正常結果,非錯誤;reason 說明未命中原因。)
2.3 8-2:角色/客層上限(參數化權限)
裁定:新增 CC_LIM.SPECIAL_LIMIT_POLICIES(V8)。
| 欄位 | 說明 |
|---|---|
id |
PK |
role_code |
角色(如 cardcore-csr/cc-payment-service/UNVERIFIED) |
segment_code |
客層;* = 不限
|
currency |
幣別 |
max_amount |
該組合之可設定上限(分) |
status |
ACTIVE/INACTIVE
|
created_at/updated_at/version
|
SQL-05 通用欄位 |
UNIQUE (role_code, segment_code, currency);索引 (role_code, status)。
2.3.1 ★★ 折衷之處理(本 CR 之重點之一)
初版設計把「角色」與「客層」兩者都推給「請求帶入」,並承認那是折衷。
動工前查證後,兩者之處置不同:
① 角色:折衷移除——改由「已驗證之權杖」決定(伺服器端)
證據:common-spring 已有 JwtRoleConverter(SCS-022 §7.2)——
將 Keycloak 之 realm_access.roles 抽為 ROLE_<name> authorities,
且 SecurityAutoConfiguration 已將其裝入 JwtAuthenticationConverter。
裁定:role_code 一律由伺服器自 Authentication 決定,不接受請求帶入(防提權)。
來源優先序(皆為已驗證權杖之事實,或明確之未驗證狀態):
| 序 | 條件 | role_code |
verified |
|---|---|---|---|
| 1 | 有 ROLE_* authorities(使用者權杖之 realm roles) |
去 ROLE_ 前綴;多筆取字典序最小(決定性) |
true |
| 2 | 無 realm roles 但為服務權杖(JwtAuthenticationToken 之 azp 存在) |
azp 值(如 cc-payment-service) |
true |
| 3 | 以上皆無(認證停用/匿名) | UNVERIFIED |
false(併記 WARN) |
★ 為何「取字典序最小」而非「取最寬鬆」:多角色使用者之語意本就不明;
取最小者決定性且不會因角色組合而放寬,並於軌跡記錄「實際使用之角色」。
★ 序 3 為 fail-closed 之樞紐:認證停用時角色為 UNVERIFIED,
必須顯式設定 role_code='UNVERIFIED' 之政策才會通過——不會「因為沒認證所以放行」。
② 客層:折衷保留,但附證據、防護與後續
為何無法自動取得(真機查證):權威來源為 CC_PRODUCT.PRODUCTS.CARD_GRADE,
惟取得路徑為「歸戶 → 卡片 → 產品 → 卡等」,而——
| # | 障礙 | 證據 |
|---|---|---|
| 1 |
CC_LIM 無任何產品/卡等欄位
|
all_tab_columns 實查 |
| 2 |
card-service 之 ACL 只接受 cc-authorization-service
|
cardcore-service-acl.yml |
| 3 |
party-service 之 ACL 不含 cc-limit-service
|
同上 |
| 4 |
CC_PARTY.ACCOUNTS 無客層欄位
|
實查(且全庫無 SEGMENT/LEVEL) |
→ 自動取得需同時開放兩個 ACL + 補讀模型,與 8-2 之價值不成比例。
裁定:segment_code 由請求帶入(可選,預設 *),並——
- 定位為「政策查詢鍵」而非「授權判斷」(真正的權限在角色,角色已不可偽造);
- 程式與文件明確標註其未經權威來源驗證;
- 列 WBS 後續:若客戶要求自動取得,需三項變更(見 §3)。
2.3.2 政策查詢與檢核
檢核時點:LimitAdjustmentService.apply(),adjustType='SPECIAL' 時——
afterAmount ≤ 適用政策之 max_amount。
查詢優先序(決定性):(role, segment) → (role, '*') → 皆無 → 拒絕
(fail-closed,CC-LIM-1002「超過可設定上限」)。
⚠️ 不設
('*','*')萬用列:那會讓「忘了設定政策」與「刻意全域放行」無法區分。
需要全域上限者,明確列出各角色即可。
2.3.3 政策之維護(8-1 之「依人員權限控管」)
新增 2 個端點:GET /api/v1/special-limit-policies(查詢)與
PUT /api/v1/special-limit-policies/{roleCode}/{segmentCode}(upsert;冪等)。
★ 停用 = PUT 帶 status=INACTIVE(不做 DELETE——政策為稽核相關設定,宜留痕)。
⚠️ 端點本身之權限:目前僅受 limit-service 之一般認證保護(任何已驗證呼叫者皆可設定)——
正式應限定特定角色;平台無 RBAC 資料源可資判斷,故如實揭露並列後續(見 §3)。
2.4 8-6/8-7:臨時額度串動
裁定:以事件驅動,於 limit-service 新增兩個消費端。
8-6(綜所稅請款 → 調高卡人臨時額度)
-
訂閱:
cc.posting.transaction.posted(SAD-002 §3.2早已載明limit為消費者,惟從未實作)。 -
前置(契約缺口):現行 payload 不含
authorizationId→ 無法識別「哪一筆用了專用額度」。
→ 補為可選欄位(SAD-004 §5.3:新增可選欄位不升版)。來源已存在
(CC_POSTING.TRANSACTIONS.AUTHORIZATION_ID),不需臆造。 -
處理:依
authorizationId查本服務之LIMIT_HOLDS→ 若hold_type='SPECIAL':- 將該筆佔用釋放(請款比對,
BR-03-02)→ 專用池used_amount -= 金額; -
調高臨時額度:
temporary_amount += 金額,temp_start = 入帳日,
temp_end = 繳款截止日 + 7 日。 - 寫入串動紀錄(見下)以供 8-7 對應。
- 將該筆佔用釋放(請款比對,
-
「繳款截止日」之來源:★ 本節原設計有誤,已於實作時更正——
原載「以CC_STMT.STATEMENTS.payment_due_date之最近一期為準」,
惟limit-service之 schema 為CC_LIM,讀CC_STMT屬跨 schema
(SAD-003/SQL-07禁止)→ 不可行。
→ 改以「入帳日 + 帳單週期(預設 25)+ 7 日」推算,
並於紀錄標記due_date_source = DERIVED——不臆造為「帳單之繳款截止日」。
可配置:cardcore.limit.special-temp-raise.statement-cycle-days/.grace-days。
精確值需statement整合(或於posted.v1補paymentDueDate)→ 列後續。
8-7(繳款 → 調降卡人臨時額度)
-
訂閱:
cc.payment.payment.posted(本服務已訂閱,見SCS-002 §7.7)→ 併入既有消費者。 -
處理:依「該歸戶尚未回收之 8-6 串動紀錄」,依先進先出回收
min(繳款金額, 未回收總額)→temporary_amount -= 回收額(下限 0),
並將對應紀錄標記recovered_at。 - ★ 不得直接將
temporary_amount歸零——該欄可能同時承載人工設定之臨時額度(§7.2之申請)。
故必須以串動紀錄精確扣回,否則會誤砍客戶經核准之臨時額度。
新增表 CC_LIM.SPECIAL_LIMIT_TEMP_ADJUSTMENTS(8-6/8-7 之串動紀錄)
| 欄位 | 說明 |
|---|---|
id |
PK |
account_id/currency
|
歸戶 × 幣別 |
special_limit_id |
來源專用額度 |
authorization_id/posting_id
|
來源(可追溯) |
raised_amount |
8-6 調高之金額 |
recovered_amount |
8-7 已回收金額(累計) |
raised_on/raise_until
|
調高日/迄日(=繳款截止日 + 7) |
due_date_source |
STATEMENT/DERIVED
|
status |
OPEN/CLOSED
|
created_at/updated_at/version
|
SQL-05 通用欄位 |
3. 分階段執行(每階段皆為「完整可驗證之交付單元」)
| 階段 | 範圍 | 內容 | 驗收 |
|---|---|---|---|
| Phase 1 |
scope_ref + 8-3/8-5
|
✅ 已完成(2026-10-02,#A-154):V7__special_limit_pool.sql(special_limits.scope_ref+limit_adjustments.scope_ref+limit_holds.special_limit_id);SpecialLimitRepository#findApplicable/increaseUsed/decreaseUsed;LimitApplicationService 之 SPECIAL 池路徑與釋放回沖;新端點 special-limits/applicable;authorization-service 之 SpecialLimitPort+三段式池選擇;契約;單測;真機 11 項
|
✅ 專用額度交易不佔卡人額度(account_limits 全程未變、availableLimit 不因專用佔用而減);池不足 → 回退卡人額度;兩者皆不足 → DECLINED 51
|
| Phase 2 | 8-2 |
V8(SPECIAL_LIMIT_POLICIES);CallerIdentityPort(★ 角色自權杖);apply() 政策檢核;政策 GET/PUT 端點;契約;單測;真機
|
不同角色之可設定上限確實生效;查無政策 → 拒絕;★ 請求帶入之角色不得提權 |
| Phase 3 | 8-6/8-7 | ✅ 已完成(2026-10-02,#A-156):posting.transaction.posted.v1 補可選 authorizationId(不升版);V9(串動紀錄表);PostingTransactionPostedListener(8-6)+ 繳款消費端併入 8-7(窄埠);raiseTemporary/lowerTemporary(★ 相對增量);契約;單測;真機 6 項(真實 Kafka + Oracle) |
✅ 請款 → 臨時額度 0 → 15000、迄日 2026-11-03(入帳日+32);繳款 10,000 → 5000、再繳 5,000 → 0 且紀錄 CLOSED;★ 專用池 used_amount 對稱回沖 15000 → 0
|
共用:一階段一遷移(V7/V8/V9)——
★ 不預先建立「有表無程式」的空表(避免重蹈 CR-014 §0「表已存在、程式全無」之覆轍)。
各階段之實作與驗證獨立,可分別交付。
4. 契約變更(破壞性與非破壞性)
| 契約 | 變更 | 性質 |
|---|---|---|
limit-service.yaml HoldRequest
|
新增可選 specialLimitId
|
非破壞(新增可選) |
limit-service.yaml |
新增 GET /accounts/{accountId}/special-limits/applicable + SpecialLimitApplicabilityView
|
非破壞(新增端點) |
limit-service.yaml SpecialLimitView
|
新增 scopeRef
|
非破壞(新增可選) |
limit-service.yaml LimitAdjustmentRequest
|
新增可選 applicantRole/applicantSegment
|
非破壞(新增可選) |
posting-service.yaml 事件 payload |
posted.v1 補可選 authorizationId
|
非破壞(SAD-004 §5.3) |
依
CR-030P4:全部為非破壞性(新增可選欄位/新增端點),不升版。
5. 已知限制(誠實揭露)
-
→ 部分解除(2026-10-02):role_code/segment_code未經身分系統驗證
★role_code已改由「已驗證之權杖」決定(JwtRoleConverter之 realm roles/服務權杖之azp),
不接受請求帶入 → 折衷移除。⚠️segment_code仍由請求帶入(附證據,見 §2.3.1 ②)。 -
客層未自動取得(
limit-service不得呼叫card-service/party-service;CC_LIM無產品欄位)
——須由呼叫端帶入。後續:需三項變更(SAD-002 §5開放cc-limit-service→card/party;
party提供歸戶→卡片;card/product提供卡等)。 - ★ 政策維護端點之權限未收斂(
§2.3.3):目前任何已驗證呼叫者皆可設定政策上限——
正式應限定特定角色,惟平台無 RBAC 資料源可資判斷 → 如實揭露,列後續。 -
專用額度之「已用」語意為「佔用中」,非「已入帳未清償」:
因專用池之請款比對訊號即為「釋放」(Phase 3 實作後,請款會釋放佔用並改以臨時額度表達)。
Phase 1~2 期間,請款不會釋放專用佔用(尚無消費者)→ 專用池之佔用只增不減,
須以人工或後續 Phase 3 收斂。 -
scope_ref為 MCC 白名單,不支援萬用字元/範圍(如93**)。 - Stand-in 期間專用額度不可用(本地讀模型無此資料)→ 一律退回卡人額度。
6. 修訂記錄
| 版本 | 日期 | 修訂內容 |
|---|---|---|
| 1.1 | 2026-10-02 | ✅ Phase 1/2/3 全數完成。Phase 1(#A-154):scope_ref + 8-3/8-5(三段式池選擇;★ BR-04-01 結構性成立)。Phase 2(#A-155):8-2(政策表 + ★ 角色自權杖——折衷移除;客層保留但附四項證據)。Phase 3(#A-156):8-6/8-7(串動紀錄 + posted.v1 補可選 authorizationId + FIFO 回收)。★ 更正 §2.4:「繳款截止日」原設計讀 CC_STMT 為跨 schema、不可行 → 改為推算並標記 DERIVED。★ 併載真機揭露之兩項陷阱:all_tab_columns 跨 schema 掃描靜默回 0 列;Keycloak 權杖之雜訊角色(default-roles-*/offline_access)會使角色解析荒謬化
|
| 1.0 | 2026-10-02 | 初版:動工前真機查證(8 項)、四項設計與裁定(scope_ref 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表)、契約變更(全為非破壞)、三階段執行計畫、五項已知限制 |
| 1.0 | 2026-10-02 | 初版:動工前真機查證(8 項)、四項設計與裁定(scope_ref 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表)、契約變更(全為非破壞)、三階段執行計畫、五項已知限制 |