CR #244 » CR-043_契約與實作四處既有不符(A-187)之診斷與待裁定.md
CR-043 契約 ↔ 實作四處既有不符(#A-187)——診斷與修法方向待裁定
| 項目 | 內容 |
|---|---|
| 文件編號 | CR-043 |
| 版本 | 1.4 |
| 狀態 | ★★ 契約/型別差異已結案——§2.1/§2.2/§2.3/§2.4(見 §8/§9)及 §3.1 七項差異處置(見 §10)皆已執行。★★ verify_contract_dto:不符 0 處、需確認 0 則;N2-d 契約一致性全掃已執行。⚠️ #7 僅為移除未實作端點宣告,不代表卡片級可用額度需求已完成;預期需求另見 CR-045 §6/GOV-006 2.4.4
|
| 提出日期 | 2026-10-15 |
| 提出者 | 開發團隊(★ BEAM PMO 2026-10-15 裁定「請立 CR」;來源為 #A-187 ⑥ 之揭露) |
| 影響文件 |
CR-019(契約↔實作比對)、TRR-001 §U-04、TRP-002 §13.4(N2-d)、SCS-001 §7.9
|
| 影響模組 |
authorization-service(3 處)、installment-service(1 處) |
| 優先級 |
P2(中)——★ 非運行期缺陷(服務可運作),惟 ★ 擋住 N2-d 契約一致性全掃,且屬「兩份真相」之風險 |
| 相關前例 |
CR-019(契約↔實作比對)、CR-041(同批 B2 揭露;本項為其 ⑥ 之另案) |
1. 摘要
tools/verify_contract_dto.py(比對 gen_openapi.py 產生之契約 vs 實作 DTO)於
2026-10-15 現況複查(★ 非抄舊結論)顯示:掃描 17 服務,不符 4 處、
需確認 7 則、INFO 14 則。★ 此 4 處即 #A-187 ⑥ 所指、
TRR-001 §U-04 所載之既有落差——★ 經核對非近期 CR 引入(疑為 CR-019 遺留)。
★ BEAM PMO 2026-10-15 裁定「請立 CR」——故本文僅診斷並提出修法方向,
★ 未動手修,待核定後另案執行。
2. 四處不符之清單(逐項)
2.1 ★ authorization-service:POST /api/v1/authorizations/{authNo}/reversals(201)
| 內容 | |
|---|---|
| 契約宣告 | 回應型別 AuthorizeResponse(gen_openapi.py 第 412–413 行) |
| 實作回傳 |
ReversalService.ReversalResult(AuthorizationController.reverse,第 123–124 行) |
| ★ 契約有而實作無 |
authorizationId/decision/respCode/holdId/holdAmount/availableLimit/checkResults/txTime
|
| ★ 實作有而契約無 |
type/amountMinor/holdReleased/reversedAt
|
2.2 ★ authorization-service:POST /api/v1/authorizations/{authNo}/overrides(200)
| 內容 | |
|---|---|
| 契約宣告 | 回應型別 AuthorizeResponse(gen_openapi.py 第 416–417 行) |
| 實作回傳 |
OverrideService.OverrideResult(AuthorizationController.override,第 218–219 行) |
| ★ 契約有而實作無 |
authorizationId/respCode/holdId/holdAmount/availableLimit/checkResults/txTime
|
| ★ 實作有而契約無 |
force/holdReleased/overriddenAt
|
★ 2.1/2.2 之共同性質:契約之回應型別誤植為
AuthorizeResponse——
★ 疑為複製貼上(三端點authorize/reversals/overrides同用一範本)。
★ 語意上,沖正回傳ReversalResult(holdReleased/reversedAt)、
人工/強制授權回傳OverrideResult(force/overriddenAt)方為正確——
★ 即AuthorizeResponse(含decision/checkResults)用於沖正/覆寫並不合理。
2.3 ★ authorization-service:schema AuthorizeRequest
| 內容 | |
|---|---|
| 契約宣告 | 含 merchantName/entryMode/emvData
|
| 實作 DTO |
AuthorizeRequest(record)無此三欄;實有 stan/cardNo/txType/amount/currency/mcc/merchantId/txTime
|
| ★ 差異性質 | 契約有、DTO 無——★ 消費端依契約送此三欄會被靜默忽略 |
2.4 ★ installment-service:schema InstallmentPeriod
| 內容 | |
|---|---|
| 契約宣告 |
無 principalAmount
|
| 實作 DTO |
InstallmentPeriod(record)有 principalAmount(第 12 行) |
| ★ 差異性質 | DTO 有、契約無——★ 消費端無從依契約取得;★ 若為必要資訊,應補契約 |
3. 查證(as-found,2026-10-15)
python3 tools/verify_contract_dto.py
→ 契約↔DTO 核對:掃描 17 服務,不符 4 處、需確認 7 則、INFO 14 則
| 查證項 | 方法 | 結果 |
|---|---|---|
| 是否近期引入 | 比對 git log 與相關 CR |
★ 非本輪引入;疑為 CR-019 遺留(與 #A-187 ⑥ 一致) |
| 契約之單一真實來源 | 檔案檢視 | ★ 契約由 tools/gen_openapi.py 產生(★ 不可手改 contracts/openapi/*.yaml) |
| 是否擋住後續工作 | 檢視 TRP-002 §13.4
|
★ 是——N2-d 契約一致性全掃待本項裁定後方可執行
|
3.1 併記:7 則需確認(★ 非「不符」,但同屬契約↔實作落差)
verify_contract_dto.py 另有 7 則 ??(需確認)——★ 其中 6 則為「僅命名差異」,
1 則為路徑寫法差異:
| # | 服務/端點 | 契約型別 | 實作型別 | 性質 |
|---|---|---|---|---|
| 1 |
authorization GET …/check-results
|
AuthCheckResult |
AuthCheckItemView |
僅命名 |
| 2 |
authorization GET …/stats
|
AuthorizationStatsView |
AuthorizationStats |
僅命名 |
| 3 |
authorization GET …/state
|
AuthorizationStateView |
AuthorizationState |
僅命名 |
| 4 |
card GET /cards/stats
|
CardStatsView |
CardStats |
僅命名 |
| 5 |
card GET /card-applications/stats
|
CardApplicationStatsView |
CardApplicationStats |
僅命名 |
| 6 |
dispute GET /disputes/stats
|
DisputeStatsView |
DisputeStats |
僅命名 |
| 7 |
limit GET /cards/{cardId}/limits
|
有端點 | Controller 無對應方法 | ★ 路徑寫法差異(同服務另有 /accounts/{}/limits;請人工確認) |
★ 命名差異之規律:契約慣用
…View後綴,實作慣用裸名。
★ 此 7 則未列為「不符」(欄位一致),惟型別名不一致對「契約驅動之產生器/客戶端」仍有影響,
一併列出供裁定(是否統一為…View)。
★★ 已結案(2026-10-15,BEAM PMO 裁定「統一命名差異」)——見 §10:
★ 統一方向為「契約 schema 名 = 實作型別名」(★ 而非契約之…View——實作型別為
domain/model之領域模型,強冠…View將污染領域層)。
# 處置 1 ★ 改實作: AuthCheckItemView→AuthCheckResult(該 nested record 之 javadoc 本即載「對應契約AuthCheckResult」;★ 契約名較佳,故改實作側)2 ★ 改契約: AuthorizationStatsView→AuthorizationStats3 ★ 改契約: AuthorizationStateView→AuthorizationState4 ★ 改契約: CardStatsView→CardStats5 ★ 改契約: CardApplicationStatsView→CardApplicationStats6 ★ 改契約: DisputeStatsView→DisputeStats7 ★ 改契約:移除stale 端點 GET /api/v1/cards/{cardId}/limits(★ 實作與SCS-002 §6皆為/accounts/{accountId}/limits,且無消費者;SDD-003/SDD-006之卡級路徑一併更正)★ 結果: verify_contract_dto→ 需確認 0 則、INFO 14 → 8 則(★ 另見 §10 之併發現)。
4. 建議修法方向(★ 逐項,待 BEAM PMO 核定)
| 項 | 建議方向 | 理由 |
|---|---|---|
2.1 reversals |
★ 修契約(gen_openapi.py):回應改為 ReversalResult(依實作欄位 type/amountMinor/holdReleased/reversedAt) |
★ 沖正不應回傳授權之 decision/checkResults;實作語意正確 |
2.2 overrides |
★ 修契約:回應改為 OverrideResult(依實作欄位 force/holdReleased/overriddenAt) |
同上;人工/強制授權有自身結果語意 |
2.3 AuthorizeRequest |
⚠️ 待裁定(二選一): (A) 修契約移除 merchantName/entryMode/emvData(若確認不需);(B) 補實作接收此三欄(若 ISO 8583 之 entryMode/EMV 資料業務確需) |
★ 此三欄屬卡組織授權語意(POS 輸入模式/EMV 資料),是否納入為業務決策,非開發可片面認定 |
2.4 InstallmentPeriod |
★ 修契約:補 principalAmount
|
★ 攤還期數之本金部分為正當回應欄位(實作已有),漏宣告為契約不足 |
★ 契約修改程序(
CSP-001/產生器紀律):①改gen_openapi.py→
②重跑產生器(★ 無命令列旗標,執行即寫入全部)→ ③以verify_contract_dto.py再實測 →
④確認 0 不符 → ⑤回寫本 CR。★ 不可手改contracts/openapi/*.yaml。
5. 待 BEAM PMO 裁定事項
- ★★ 2.1/2.2:是否同意「修契約(改宣告為
ReversalResult/OverrideResult)」? - ★★ 2.3:
AuthorizeRequest之merchantName/entryMode/emvData——修契約移除 或 補實作接收? - ★ 2.4:是否同意「補契約(新增
principalAmount)」? - ★ 3.1 之 7 則:是否一併納入本 CR 處理(統一
…View命名/修正limit路徑)?
6. 影響
- ★ 擋住
N2-d(契約一致性全掃,TRP-002 §13.4)——本 CR 裁定並執行後,N2-d方可啟動。 - ★ 「兩份真相」風險:契約與實作不一致時,依契約產生之客戶端/測試將與實作行為不符
(同MEMORY-RULES所載「契約 ↔ 實作漂移」之工具盲區)。
7. 如實揭露
- ★ 本文未修任何程式或契約——依裁定「請立 CR」,僅立與診斷。
- ★ 未宣稱此 4 處曾造成運行期故障——★ 屬契約治理落差,非服務故障。
- ★ 2.1/2.2 之「複製貼上」為研判(契約三端點同範本),★ 非逐行溯源之定論;
惟欄位差異與語意不合理足以支撐「契約誤植」之方向。
8. ★ §2.3 之執行(2026-10-15,BEAM PMO 裁定「請補實作接收」)
★ BEAM PMO 2026-10-15 裁定:§2.3 之
AuthorizeRequest三欄採 (B) 案「補實作接收」
(非 §4 之 (A)「修契約移除」)。★ 其餘 §2.1/§2.2/§2.4 本次未裁定,仍待核定。
8.1 盤點(動工前,逐項實查——★ 不照清單字面做)
| 項 | 動查結果 |
|---|---|
| 契約 |
gen_openapi.py 第 216–219 行已宣告 merchantName(maxLength 80)/entryMode(enum CHIP/MAGSTRIPE/CONTACTLESS/EC/MANUAL/QR)/emvData(object,nullable)→ ★ 契約無須改
|
| DTO |
AuthorizeRequest(record)無此三欄 → 消費端依契約送三欄被靜默忽略
|
| DDL(動查) | ★ authorizations.merchant_name VARCHAR2(80 CHAR)(V1__baseline.sql 第 26 行)已存在;★ entry_mode VARCHAR2(16 CHAR)(第 29 行)已存在;★ emv_data 不存在
|
| 實作落差 | ★ JdbcAuthorizationRepository 之 INSERT 未列 merchant_name、entry_mode 綁死 null → 兩欄恆為 NULL(「欄位在、資料不進」);AuthorizeCommand 之 entryMode 原已存在惟控制器傳 null
|
8.2 執行(規格先行 → 實作 → 實測)
-
規格先行:
SCS-001→ 1.15(新增 §7.17「授權請求之特店名稱/輸入模式/EMV 資料之接收」;§5 資料模型表補三欄);SDD-001 §3之auth.authorizationsDDL 補emv_data jsonb(設計真相)+ 變更交付說明補V10。 -
遷移:新增
V10__authorization_entry_emv.sql(ALTER TABLE authorizations ADD (emv_data JSON);merchant_name/entry_mode早已存在,只接線不新增)。 -
實作(DDD 四層):
-
AuthorizeRequest(interfaces):補merchantName(@Size(max=80))/entryMode(@Patternenum)/emvData(Map<String,Object>)。 -
AuthorizeCommand(application):補merchantName/emvData(entryMode原已存在)。 -
Authorization(domain):補三欄(emvData防禦性複製為不可變)+decide(...)參數 + accessor。 -
AuthorizationController.toCommand:三欄如實傳遞(此前entryMode傳null)。 -
AuthorizeUseCase:三欄傳入decide(...)。 -
JdbcAuthorizationRepository:INSERT 補merchant_name/entry_mode(實值)/emv_data(JSON(?),以 Jackson 序列化;比照JdbcPartyRepository)。
-
-
回應不擴充:
AuthorizeResponse契約未宣告此三欄 → 本期不加入回應(避免契約外欄位漂移)。
8.3 實測(2026-10-15)
| 查證項 | 命令/方法 | 結果 |
|---|---|---|
| 契約↔DTO 不符數 | python3 tools/verify_contract_dto.py |
★★ 4 處 → 3 處——★ AuthorizeRequest 一項已消失(即本項已閉合);餘 3 處為 §2.1/§2.2/§2.4(未裁定) |
| 單元/整合測試 | ./gradlew :services:authorization-service:test |
★ 148 測 0 失敗(新增 4:AuthorizeRequest 接收、非法 entryMode→400、聚合傳遞、三欄落庫直查 DB) |
| 三欄落庫(防假綠) | 整合測試直查 DB:SELECT merchant_name, entry_mode, JSON_SERIALIZE(emv_data ...)
|
★ 三欄確實落庫;★ 未提供時為 SQL NULL(非空字串/非 JSON null) |
| SQL 遷移 | python3 tools/validate_sql.py backend |
★ 掃描 71 檔,違規 0 處 |
| 掃描器 |
scan_scaffold/scan_mockdata/scan_faketest(authorization-service) |
★ 皆 0 命中 |
8.4 未竟(如實揭露)
- ★ §2.1/§2.2/§2.4 尚未執行——★ 本次 BEAM PMO 僅裁定 §2.3;★ 其餘三項(
reversals/overrides回應型別、InstallmentPeriod.principalAmount)仍待核定。
★ (後續:BEAM PMO 2026-10-15 已裁定執行 → 見 §9) - ★
N2-d契約一致性全掃須待 §2.1/§2.2/§2.4 全數閉合後方可啟動(現餘 3 處不符)。
★ (後續:三項已執行 → 不符 0 處 →N2-d已啟動,見 §9)
9. ★ §2.1/§2.2/§2.4 之執行(2026-10-15,BEAM PMO 裁定「接受建議」)
★ BEAM PMO 2026-10-15 裁定:「執行與接受後面的建議:reversals/overrides 回應型別(建議修契約為
ReversalResult/OverrideResult)+InstallmentPeriod.principalAmount(建議補契約)」。
9.1 盤點(動工前,逐項實查——★ 不照清單字面做)
| 項 | 動查結果 |
|---|---|
| §2.1/§2.2 契約 |
gen_openapi.py 第 412–418 行:reversals/overrides 之回應皆 AuthorizeResponse(★ 疑為三端點同範本之複製貼上) |
| §2.1/§2.2 實作 |
ReversalService.ReversalResult(authNo/type/amountMinor/holdReleased/reversedAt);OverrideService.OverrideResult(authNo/decision/force/holdReleased/overriddenAt)。★ 實查值域:type∈{FULL,PARTIAL}(type.name())、decision∈{APPROVE,DECLINE}(act.name()) |
| §2.4 契約 |
InstallmentPeriod schema 無 principalAmount
|
| ★ §2.4 實作(關鍵盤點) | ★ principalAmount 僅在領域模型 InstallmentPeriod;★ 回應 InstallmentView.PeriodView 未暴露 → ★ 若僅補契約,將宣告「回應不存在之欄位」=新的不一致
|
9.2 執行(規格先行 → 契約 → 實作 → 實測)
-
§2.1/§2.2:
gen_openapi.py新增ReversalResult/OverrideResultschema;兩端點回應$ref改之(依實作欄位)。 -
§2.4:
InstallmentPeriodschema 補principalAmount;★ 併於回應PeriodView+InstallmentViewAssembler暴露之(使契約 = 回應 = 領域模型三者一致)。 -
契約重生:
python3 tools/gen_openapi.py→ ★ 0 錯誤(96 端點)。 -
規格:
SCS-001→ 1.16(§6 API 表 + §7.6/§7.8.2);SCS-009→ 1.6(§3/§6)。
9.3 實測(2026-10-15)
| 查證項 | 命令 | 結果 |
|---|---|---|
| 契約重生 | python3 tools/gen_openapi.py |
★ 0 錯誤(96 端點) |
| 契約↔DTO | python3 tools/verify_contract_dto.py |
★★ 不符 0 處(原 4 處) |
| 工具自測 | python3 tools/verify_contract_dto.py --selftest |
★ 8/8 通過(含正/負樣本) |
| 服務測試 |
:services:installment-service:test / :services:authorization-service:test
|
★ 49 測 0 失敗 / 148 測 0 失敗 |
| SQL/掃描器 |
validate_sql backend + 三掃描器 |
★ 0 違規 / 0 命中 |
★ 不符數歷程:4 處(立 CR 時)→ 3 處(§2.3)→ 0 處(§2.1/§2.2/§2.4)。
★ N2-d(契約一致性全掃)已解除阻塞並執行——見 TRP-002 §13.5、docs/08-evidence/N2_契約一致性全掃.md。
9.4 未竟(如實揭露)
- ★ §3.1 之 7 則「需確認」未結案——6 則「僅命名差異(契約慣用
…View、實作慣用裸名)」+ 1 則limitGET /cards/{cardId}/limits路徑寫法;★ 本次未處理(★ 屬另一裁定事項)。
★ (後續:BEAM PMO 2026-10-15 已裁定「統一命名差異」→ 見 §10) - ★ §2.4 之語意提醒(不代為認定):現行實作之
amount與principalAmount同值
(DDL 分列AMOUNT/PRINCIPAL_AMOUNT;生成器InstallmentPlan以同一principal填入兩者)。
★ 兩欄是否應有語意差異(如amount=本金+手續費)屬業務語意,★ 本次未認定,僅如實揭露。
10. ★ §3.1 之 7 則「需確認」之執行(2026-10-15,BEAM PMO 裁定「統一命名差異」)
★ BEAM PMO 2026-10-15 裁定:「
CR-043 §3.1之 7 請幫忙統一命名差異,記得回頭更新相關文件。」
10.1 統一方向之裁定(★ 動工前盤點)
★ 契約慣用 …View、實作慣用裸名(見 §3.1)。★ 兩方向之評估:
| 方向 | 評估 |
|---|---|
(A) 統一為 …View(改實作側) |
★ 不採——★ 實作型別為 domain/model 之領域模型(AuthorizationStats/CardStats/…),強冠 …View 將污染領域層,且牽動 domain/application/infrastructure/測試之大量引用 |
| (B) 統一為實作型別名(改契約側) | ★ 採——★ 契約 schema 名鏡射實際序列化型別;★ 改動限於 gen_openapi.py + 重生(除 #1 外零 Java 變更),風險最低 |
★ 例外(#1):AuthCheckItemView 之 javadoc 本即載「對應契約 AuthCheckResult」→ ★ 契約名較佳,故改實作側(AuthCheckItemView → AuthCheckResult)。
10.2 執行
-
契約(
gen_openapi.py+ 重生):#2~#6 之 schema 更名(+端點$ref);#7 清除契約中無實作之端點宣告GET /api/v1/cards/{cardId}/limits(+其 description)。此為契約清理,不是卡片級查詢功能實作。 -
實作:
AuthorizationController之 nested recordAuthCheckItemView→AuthCheckResult(3 處引用;★ 零測試引用)。 -
★★ 併發現並修正(超出 §3.1 之原 7 則):契約
AuthorizeResponse.checkResults之 items 原引用AuthCheckResult(checkCode/checkOrder/result/reasonCode,屬落庫稽核列語意),惟實作AuthorizeResponse.CheckResultView為(checkCode/passed/respCode/message/implemented)——★ 欄位不符(★ 因巢狀,verify_contract_dto之關卡看不到)。★ 已補 schemaCheckResultView並改引用,使契約=實作。 -
文件:
SDD-003(時序圖路徑)/SDD-006(契約表)改為 account 級;SCS-001 §6/§7.10/§7.11、SCS-003 §7.9.1、SCS-012 §7.x更新型別名。
10.3 實測(2026-10-15)
| 查證項 | 命令 | 結果 |
|---|---|---|
| 契約重生 | python3 tools/gen_openapi.py |
★ 0 錯誤(96 → 95 端點,移除 #7) |
| 契約↔DTO | python3 tools/verify_contract_dto.py |
★★ 需確認 7 則 → 0 則;INFO 14 → 8 則;不符 0
|
| 服務測試 | :services:authorization-service:test |
★ 148 測 0 失敗(#1 更名後) |
| SQL/掃描器 |
validate_sql backend + 三掃描器 |
★ 0 違規/0 命中 |
10.4 如實揭露
- ★★
GOV-006曾誤記「已補正」:GOV-006(第 19 列)載「limit-service路徑維度差異…已於CR-019批 1 補正」——★ 惟本次實查:契約仍為卡級路徑、實作僅 account 級 → ★ 該「已補正」為不實(★ 幽靈結案)。★ 本次已真正補正並更正該列。 - ★ #7 的結案範圍僅為契約清理(96 → 95):原端點無實作、無消費者,故移除其 OpenAPI 宣告正確;★ 但 BEAM PMO 2026-10-15 已明示「客戶最終一定會要求卡片級可用額度查詢」——不得把移除端點寫成該業務需求結案。現已依
CR-045 §6於GOV-006 2.4.4登錄預期需求,待額度分攤模型凍結後另立 CR;不恢復 stale endpoint。 - ★ 併發現之
AuthorizeResponse.checkResults欄位不符(§10.2 第 3 點)原不在 §3.1 之 7 則內——★ 因巢狀而屬工具盲區;★ 本次一併修正,如實記錄。
10.5 ★ 更正 #7 結案邊界(2026-10-15)
前版將 #7 標為「移除 stale 端點」且記「§3.1 七則全數結案」,此結論只適用於契約不符項目,
容易被誤讀為「卡片級可用額度查詢不再待辦」。現依 BEAM PMO 對客戶後續需求之明確提醒更正如下:
-
已完成:移除沒有實作/消費者的 OpenAPI 路徑;端點數 96 → 95;
SDD-003/SDD-006改為帳戶級契約路徑。 - 未完成且非本 CR 範圍:卡片/附卡/企業子額度之可用額度分攤模型、查詢端點與客戶需求細節。
-
追蹤:
CR-045 §6建議先完成額度多維度規格,並已在GOV-006 2.4.4建立預期需求準備項;待依賴閉合後另立 CR。 - 禁止回退:不得把 stale 路徑復原成「看似已交付」的契約;未來端點須由新 CR 與生成器規格建立。
11. 變更紀錄
| 版本 | 日期 | 變更 |
|---|---|---|
| 1.4 | 2026-10-15 | 更正 §3.1 #7 結案邊界:明確區分移除未實作 OpenAPI 宣告(已完成)與卡片級可用額度查詢預期需求(未完成);追蹤至 CR-045 §6/GOV-006 2.4.4。 |