Project

General

Profile

CR #244 » CR-043_契約與實作四處既有不符(A-187)之診斷與待裁定.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

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 → AuthorizationStats
3 ★ 改契約:AuthorizationStateView → AuthorizationState
4 ★ 改契約:CardStatsView → CardStats
5 ★ 改契約:CardApplicationStatsView → CardApplicationStats
6 ★ 改契約:DisputeStatsView → DisputeStats
7 ★ 改契約:移除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 裁定事項

  1. ★★ 2.1/2.2:是否同意「修契約(改宣告為 ReversalResult/OverrideResult)」?
  2. ★★ 2.3:AuthorizeRequest 之 merchantName/entryMode/emvData——修契約移除 或 補實作接收?
  3. ★ 2.4:是否同意「補契約(新增 principalAmount)」?
  4. ★ 3.1 之 7 則:是否一併納入本 CR 處理(統一 …View 命名/修正 limit 路徑)?

6. 影響

  • ★ 擋住 N2-d(契約一致性全掃,TRP-002 §13.4)——本 CR 裁定並執行後,N2-d 方可啟動。
  • ★ 「兩份真相」風險:契約與實作不一致時,依契約產生之客戶端/測試將與實作行為不符
    (同 MEMORY-RULES 所載「契約 ↔ 實作漂移」之工具盲區)。

7. 如實揭露

  1. ★ 本文未修任何程式或契約——依裁定「請立 CR」,僅立與診斷。
  2. ★ 未宣稱此 4 處曾造成運行期故障——★ 屬契約治理落差,非服務故障。
  3. ★ 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 執行(規格先行 → 實作 → 實測)

  1. 規格先行:SCS-001 → 1.15(新增 §7.17「授權請求之特店名稱/輸入模式/EMV 資料之接收」;§5 資料模型表補三欄);SDD-001 §3 之 auth.authorizations DDL 補 emv_data jsonb(設計真相)+ 變更交付說明補 V10。
  2. 遷移:新增 V10__authorization_entry_emv.sql(ALTER TABLE authorizations ADD (emv_data JSON);merchant_name/entry_mode 早已存在,只接線不新增)。
  3. 實作(DDD 四層):
    • AuthorizeRequest(interfaces):補 merchantName(@Size(max=80))/entryMode(@Pattern enum)/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)。
  4. 回應不擴充: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 執行(規格先行 → 契約 → 實作 → 實測)

  1. §2.1/§2.2:gen_openapi.py 新增 ReversalResult/OverrideResult schema;兩端點回應 $ref 改之(依實作欄位)。
  2. §2.4:InstallmentPeriod schema 補 principalAmount;★ 併於回應 PeriodView + InstallmentViewAssembler 暴露之(使契約 = 回應 = 領域模型三者一致)。
  3. 契約重生:python3 tools/gen_openapi.py → ★ 0 錯誤(96 端點)。
  4. 規格: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 則 limit GET /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 執行

  1. 契約(gen_openapi.py + 重生):#2~#6 之 schema 更名(+端點 $ref);#7 清除契約中無實作之端點宣告 GET /api/v1/cards/{cardId}/limits(+其 description)。此為契約清理,不是卡片級查詢功能實作。
  2. 實作:AuthorizationController 之 nested record AuthCheckItemView → AuthCheckResult(3 處引用;★ 零測試引用)。
  3. ★★ 併發現並修正(超出 §3.1 之原 7 則):契約 AuthorizeResponse.checkResults 之 items 原引用 AuthCheckResult(checkCode/checkOrder/result/reasonCode,屬落庫稽核列語意),惟實作 AuthorizeResponse.CheckResultView 為(checkCode/passed/respCode/message/implemented)——★ 欄位不符(★ 因巢狀,verify_contract_dto 之關卡看不到)。★ 已補 schema CheckResultView 並改引用,使契約=實作。
  4. 文件: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 如實揭露

  1. ★★ GOV-006 曾誤記「已補正」:GOV-006(第 19 列)載「limit-service 路徑維度差異…已於 CR-019 批 1 補正」——★ 惟本次實查:契約仍為卡級路徑、實作僅 account 級 → ★ 該「已補正」為不實(★ 幽靈結案)。★ 本次已真正補正並更正該列。
  2. ★ #7 的結案範圍僅為契約清理(96 → 95):原端點無實作、無消費者,故移除其 OpenAPI 宣告正確;★ 但 BEAM PMO 2026-10-15 已明示「客戶最終一定會要求卡片級可用額度查詢」——不得把移除端點寫成該業務需求結案。現已依 CR-045 §6 於 GOV-006 2.4.4 登錄預期需求,待額度分攤模型凍結後另立 CR;不恢復 stale endpoint。
  3. ★ 併發現之 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。
    (1-1/1)