Project

General

Profile

CR #205 » CR-004_平台層錯誤映射缺口.md

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

 

CR-004 平台層 500-vs-400 錯誤映射缺口(GlobalExceptionHandler)

項目 內容
文件編號 CR-004
版本 1.1
狀態 已核定/已執行(2026-09-19 BEAM PMO 核定;同日執行完成並實測通過)
提出日期 2026-09-19
核定日期 2026-09-19
完成日期 2026-09-19
提出者 開發團隊(SDD-005 值域稽核後之待辦彙整時發現;SCS-010 §10.4 #15 曾局部揭露)
影響文件 CSP-003 §2/§3/§7、SDD-006 §3.3/§3.4、SCS-010 §10.4、SCS-018
影響服務 全部 17 個服務(common-spring 之共用元件)
優先級 高(P1)——客戶端錯誤一律回 500 並觸發告警,且違反全部 17 份契約
核定結論 4 項待決全數核准;細節授權開發團隊安排(詳 §6)
驗收結果 6/6 全對(2 服務各自前後對比);請求層錯誤 0 筆 ERROR 日誌(詳 §4.1)

1. 摘要

common-spring 之 GlobalExceptionHandler 僅有 2 個 handler
(DomainException、Exception)。Spring MVC 自身拋出之請求層例外
(主體不可解析、必要標頭缺失、方法不支援、路徑不存在……)
皆非 DomainException 子類,因而落入 Exception 兜底分支 →
一律回 500 CC-PLT-5000。

後果有三:

  1. 契約違反:全部 17 份 OpenAPI 契約對這些情境宣告 400/404/405。
  2. 告警污染:GlobalExceptionHandler 自身之日誌策略為「5xx 記 ERROR 並附完整堆疊
    (伺服器問題,需告警)」。客戶端送錯格式即產生 ERROR 與告警——
    告警疲勞將使真正的伺服器故障被淹沒。
  3. 實作分歧:rewards-service 為繞開此缺陷,放棄使用 Bean Validation、
    改以手寫檢核(SCS-010 §10.4 #15)。此繞道使各服務之驗證風格不一致。

2. 現況與實測證據

以下為 2026-09-19 實測(installment-service,埠 8089,本地 Oracle 26ai FREEPDB1),
非推測。實作路由為 /api/v1/installments(非 /installment-plans)。

# 情境 契約要求 實測結果 判定
① POST 缺必要標頭 X-Channel 400 500 CC-PLT-5000 ❌ 缺陷
② POST 請求主體為非法 JSON({bad json) 400 500 CC-PLT-5000 ❌ 缺陷
③ 路徑變數 UUID 格式錯誤 400 400 CC-INS-4000 ✅ 正確(服務自行檢核後拋 DomainException)
④ DELETE(不支援之方法) 405 500 CC-PLT-5000 ❌ 缺陷
⑤ 不存在的路徑 404 500 CC-PLT-5000 ❌ 缺陷
⑥ 查無資源(合法 UUID 但不存在) 404 404 CC-INS-4040 ✅ 正確(DomainException)

6 項中 4 項錯誤。

③/⑥ 為何正確:服務在進入 Spring MVC 之例外處理前自行檢核,
拋出已登錄之 DomainException(CC-INS-4000→400、CC-INS-4040→404)。
這正是繞道——每個服務都得自己重做一次框架本該做的事。

2.1 根因

@ExceptionHandler(DomainException.class)  // ← 業務例外:正確映射
@ExceptionHandler(Exception.class)        // ← 兜底:一律 500

Spring MVC 之請求層例外(MissingRequestHeaderException、
HttpMessageNotReadableException、HttpRequestMethodNotSupportedException、
NoResourceFoundException……)均為 Exception 之直接或間接子類,
但非 DomainException
,故全部落入兜底。

2.2 影響範圍(實測統計)

項 數值
引用 common.spring 之服務 17 / 17
自行加 @ExceptionHandler 之服務 0
受影響契約 17 份(全部)

由於缺陷位於共用元件,一次修正即覆蓋全部 17 個服務。


3. 建議修法

3.1 新增錯誤碼(須先擴充 SDD-006 §3.4)

依 CSP-003 §2「Coder 不得自創錯誤碼;新增碼須先擴充 SDD-006 §3.4」之程序,
擬新增 5 碼。惟現行類別位定義無處可放(見 §3.2),故同時提請擴充類別位語意。

原提案(立 CR 時):

擬增碼 HTTP 名稱 涵蓋之例外
CC-PLT-4000 400 請求格式錯誤 HttpMessageNotReadableException、MethodArgumentTypeMismatchException、HttpMediaTypeNotSupportedException
CC-PLT-4001 400 必要輸入缺失 MissingRequestHeaderException、MissingServletRequestParameterException、MissingPathVariableException
CC-PLT-4002 400 欄位驗證失敗 MethodArgumentNotValidException、HandlerMethodValidationException、ConstraintViolationException
CC-PLT-4003 405 HTTP 方法不支援 HttpRequestMethodNotSupportedException
CC-PLT-4040 404 路徑或資源不存在 NoResourceFoundException、NoHandlerFoundException

實作定案(核定後,授權開發團隊調整細節;已寫入 SDD-006 §3.4):

定案碼 HTTP 名稱 涵蓋之例外
CC-PLT-4001 400 缺少必要請求標頭或參數 MissingRequestHeaderException、MissingServletRequestParameterException
CC-PLT-4002 400 請求主體格式錯誤 HttpMessageNotReadableException
CC-PLT-4003 400 請求參數驗證失敗 MethodArgumentNotValidException、HandlerMethodValidationException、ConstraintViolationException、MethodArgumentTypeMismatchException
CC-PLT-4040 404 資源或路徑不存在 NoResourceFoundException
CC-PLT-4050 405 HTTP 方法不支援 HttpRequestMethodNotSupportedException

定案與提案之差異(3 項,均為刻意調整):

# 差異 理由
1 提案之 CC-PLT-4000(請求格式錯誤)未採用;其涵蓋之 HttpMessageNotReadableException 改歸 4002 避免同一 HTTP 400 出現兩個語意相近之碼(4000 與 4002)。4000 保留未用,可作日後「通用 400」之空間
2 方法不支援由 4003 移至 4050 使後兩碼對齊 HTTP 狀態碼(4001/4002/4003→400、4040→404、4050→405)。此規則與既有服務碼(CC-INS-4000→400、CC-INS-4040→404)之編號習慣一致,且可由碼反推狀態,便於人眼判讀
3 提案之 MissingPathVariableException、HttpMediaTypeNotSupportedException、NoHandlerFoundException 未涵蓋 見 §3.5 之逐項理由

⚠️ 編號差異為文件間須對齊之處:本 CR §3.1 原提案表已失效,
權威版本為 SDD-006 §3.4(實作定案)。

3.2 擴充類別位語意(原「須裁定」,已裁定:同意)

CSP-003 §2 之修正前類別位:

類別位 意義 HTTP
1 業務規則拒絕 422
2 併發/冪等衝突 409
3 狀態機衝突 409
4 資源與重複 404 / 409
5 外部與基礎設施 500 / 503

問題:請求格式錯誤(400)與方法不支援(405)不屬任何現行類別位
——它們既非業務規則拒絕(422),亦非資源與重複(404/409)。

裁定:將類別位 4 之語意由「資源與重複」擴充為「請求與資源」,
涵蓋 4xx 之請求層錯誤(400/404/405/409)。
此為最小改動(不動既有 5 個類別位之編號與其他碼),且與 CC-INS-4000(400)、
CC-INS-4040(404)等既有服務碼之編號習慣一致。

已落地:

文件 位置 內容
CSP-003 1.3 §2 類別位表、新增 §2.1 類別位 4 改為「請求與資源」;新增四子語意表與選碼判準表(判準=「尚未進入業務邏輯」)
SDD-006 1.6 §3.3 類別位表、新增 §3.3.1 同步擴充;載明擴充理由與設計原則

替代方案(已否決):新增類別位 6 專供請求層錯誤。缺點為同一 HTTP 400
可能來自類別位 1(業務規則)或 6(格式),編號語意分裂。

3.3 修正 GlobalExceptionHandler(common-spring)——已完成

於 common-spring 之 GlobalExceptionHandler 新增對應之 @ExceptionHandler 方法。
須遵守既有二原則:

  1. 不得洩漏內部實作(CSP-003 §7 E2/E4)——回應之 message 一律為
    通用文案,不得回傳原始例外訊息(HttpMessageNotReadableException
    之訊息含 Jackson 內部路徑與類別名)。診斷細節僅寫入日誌。
  2. 日誌分級不變——4xx 記 WARN、5xx 記 ERROR 並附堆疊。

實作結果:GlobalExceptionHandler 由 2 個 handler 增為 7 個
(1 領域 + 5 請求層 + 1 兜底),請求層 5 個方法共涵蓋 9 種例外型別:

方法 涵蓋例外 對應碼
handleMissingRequestValue MissingRequestHeaderException、MissingServletRequestParameterException CC-PLT-4001
handleNotReadable HttpMessageNotReadableException CC-PLT-4002
handleParameterValidation MethodArgumentNotValidException、HandlerMethodValidationException、ConstraintViolationException、MethodArgumentTypeMismatchException CC-PLT-4003
handleNoResource NoResourceFoundException CC-PLT-4040
handleMethodNotSupported HttpRequestMethodNotSupportedException CC-PLT-4050

共用私有輔助 requestError(...):

  • HTTP 狀態以 ErrorCodes 為權威(ErrorCodes.httpStatusOf(code))——避免此處硬編而與登錄表漂移。
  • 一律記 WARN,且日誌僅記例外之類別名,不記訊息與堆疊(避免請求主體內容入日誌)。
  • 回應 message 一律為固定通用字串;details 僅放欄位名與規則,不含輸入值。

兜底分支 Exception 維持不變(真正的未預期例外仍須 500 並告警)。

3.3.1 實作時發現並補強之缺口

# 缺口 處置
1 collectFieldDetails 原未處理 HandlerMethodValidationException → 該類驗證失敗會回空 details 已補:改由 getParameterValidationResults() 取參數名(取不到時退回 參數[i])與 getResolvableErrors() 之預設訊息
2 NoResourceFoundException 不在 spring-web,而在 spring-webmvc common-spring/build.gradle.kts 補 compileOnly("org.springframework:spring-webmvc")(比照既有 jakarta.servlet 之 compileOnly 慣例);另補 compileOnly("jakarta.validation:jakarta.validation-api") 供 ConstraintViolationException
3 NoResourceFoundException 之建構子為 (HttpMethod httpMethod, String requestUri, String resourcePath)——getResourcePath() 取第 3 個參數,第 2 個僅用於例外訊息 已依官方原始碼確認;單元測試依此建構(此為測試正確性之關鍵,用錯參數會使斷言失效)
4 MethodValidationResult.emptyResult() 為 Spring 之 stub,其 getParameterValidationResults() 會拋 UnsupportedOperationException 不可作為測試 fixture;改用 MethodValidationResult.create(...) + ParameterErrors 建構真實結果。已記入測試註解

3.5 未涵蓋之例外(逐項理由)

例外 原提案之碼 未涵蓋理由
MissingPathVariableException CC-PLT-4001 僅在程式路徑模板與方法簽章不符時拋出——屬程式錯誤而非客戶端錯誤,回 500 並告警為正確行為(可及早發現缺陷)
HttpMediaTypeNotSupportedException CC-PLT-4000 需 Content-Type 不符(415)才會觸發。本專案 17 份契約之寫入端點一律 application/json,且 Spring 之 @RequestBody 對缺 Content-Type 之處理先落 HttpMessageNotReadableException。未實測到觸發路徑,故不先立碼(避免「有碼無情境」)
NoHandlerFoundException CC-PLT-4040 需 spring.mvc.throw-exception-if-no-handler-found=true。Spring Boot 3.2+ 之預設路徑已改走 NoResourceFoundException(已實測命中),故 NoHandlerFoundException 為不可達

原則:不為「可能用到」而預立錯誤碼。若日後實測到觸發路徑,再依 CSP-003 §2 程序補碼。

3.4 修正後須一併處理——執行狀態

# 項目 說明 狀態
1 移除 rewards-service 之繞道 SCS-010 §10.4 #15 載明其「不用 Bean Validation」;修正後應改回標準作法,並更新該節 ✅ 已完成——RewardController/RedemptionController 之標頭改回預設 required = true,移除手動 requireChannel 檢核;Requests.requireChannel() 刪除;SCS-010 升 1.4
2 新增測試 於 common-spring 建立對 5 類例外之單元測試;並於各服務補整合測試(TRP-001) ✅ 已完成(單元)——GlobalExceptionHandlerTest 由 8 增至 18 測試(新增 @Nested 請求層 10 項,含「不得落回 500」之回歸守門);服務層整合測試見 §4.1 之端到端實測
3 契約對齊 確認 17 份契約之 400/404/405 回應已宣告(多數已宣告,須逐一核對) 🟡 部分——rewards-service.yaml 未列 CC-RWD-4000,故本次改動不影響契約;17 份之逐一核對列為後續(見 §5 註)
4 錯誤碼重生 改 SDD-006 §3.4 後執行 tools/gen_error_codes.py --write(禁止手改 ErrorCodes.java) ✅ 已完成——ErrorCodes.java 由 107 增至 112 碼;gen_error_codes.py --check 回「112 個|內容一致」

4. 驗收條件(修正後重測 §2 之 6 項)——已通過

# 情境 期望(原) 實測(修正後) 判定
① 缺必要標頭 400 CC-PLT-4001 400 CC-PLT-4001 ✅
② 非法 JSON 400 CC-PLT-4000 → 定案為 CC-PLT-4002 400 CC-PLT-4002 ✅
③ 路徑 UUID 格式錯誤 400(服務自檢,維持不變) 400 CC-INS-4000/CC-RWD-4000 ✅
④ 不支援之方法 405 CC-PLT-4003 → 定案為 CC-PLT-4050 405 CC-PLT-4050 ✅
⑤ 不存在之路徑 404 CC-PLT-4040 404 CC-PLT-4040 ✅
⑥ 查無資源 404(服務自檢,維持不變) 404 CC-INS-4040/CC-RWD-4040 ✅

且:①/②/④/⑤ 不得再產生 ERROR 級日誌(應為 WARN)。→ ✅ 實測 0 筆 ERROR。

4.1 實測證據(2026-09-19)

方法:兩個服務各自做同服務前後對比(同一組路徑、同一份腳本),
以排除「換服務導致差異」之可能。舊版為修正前仍在執行之程序;新版為重新編譯啟動。

A. installment-service(埠 8089;§2 原基準量測處)

# 情境 修正前 修正後
① 缺 X-Channel 500 CC-PLT-5000 ❌ 400 CC-PLT-4001 ✅
② 非法 JSON 500 CC-PLT-5000 ❌ 400 CC-PLT-4002 ✅
③ 路徑非 UUID 400 CC-INS-4000 ✅ 400 CC-INS-4000 ✅
④ DELETE 不支援 500 CC-PLT-5000 ❌ 405 CC-PLT-4050 ✅
⑤ 路徑不存在 500 CC-PLT-5000 ❌ 404 CC-PLT-4040 ✅
⑥ 查無資源 404 CC-INS-4040 ✅ 404 CC-INS-4040 ✅

修正前 6 項中 4 項錯誤(與 §2 完全吻合,證明基準可重現);修正後 6/6 全對。

B. rewards-service(埠 8091;繞道移除處)

# 情境 修正前(含繞道) 修正後(繞道已移除)
① 缺 X-Channel 400 CC-RWD-4000 ⚠️ 繞道 400 CC-PLT-4001 ✅ 平台統一
② 非法 JSON 500 CC-PLT-5000 ❌ 400 CC-PLT-4002 ✅
③ 路徑非 UUID 400 CC-RWD-4000 ✅ 400 CC-RWD-4000 ✅
④ DELETE 不支援 500 CC-PLT-5000 ❌ 405 CC-PLT-4050 ✅
⑤ 路徑不存在 500 CC-PLT-5000 ❌ 404 CC-PLT-4040 ✅
⑥ 查無資源 404 CC-RWD-4040 ✅ 404 CC-RWD-4040 ✅

① 修正前為 400 但碼是 CC-RWD-4000(服務自檢)——這正是繞道之指紋:
結果對,但由服務自行重做框架該做的事。修正後改由平台統一產生 CC-PLT-4001,
且服務程式碼更短(少一次手動檢核)。

C. 日誌分級

服務 6 項情境產生之 ERROR WARN
installment-service 0 6
rewards-service 0 6

日誌格式(實錄):

WARN ... c.b.c.c.s.error.GlobalExceptionHandler : 請求層例外:code=CC-PLT-4001,
     type=MissingRequestHeaderException, traceId=b9f2a4b854c04e8a8c9efd2ee997e7b8

僅記例外類別名(type=...),不含例外訊息——避免請求主體內容(可能含個資)入日誌。

D. 回應主體(不得洩漏內部實作)

# 回應
① {"error":{"code":"CC-PLT-4001","message":"缺少必要請求標頭或參數","details":[{"field":null,"reason":"缺少必要標頭:X-Channel"}]}}
② {"error":{"code":"CC-PLT-4002","message":"請求主體格式錯誤","details":[]}}
④ {"error":{"code":"CC-PLT-4050","message":"HTTP 方法不支援","details":[{"field":null,"reason":"該路徑支援的方法:GET"}]}}
⑤ {"error":{"code":"CC-PLT-4040","message":"資源或路徑不存在","details":[{"field":null,"reason":"不存在的路徑:api/v1/no-such-endpoint"}]}}

② 之 details 為空——Jackson 之解析訊息(含 [Source: ...]、內部類別名)未外洩 ✅。

E. 自動化驗證

項 指令 結果
編譯 :platform:common-spring:compileJava BUILD SUCCESSFUL
單元測試 :platform:common-spring:test 41 測試/0 失敗(GlobalExceptionHandlerTest 18)
服務測試 :services:rewards-service:test、:services:installment-service:test BUILD SUCCESSFUL
錯誤碼一致性 gen_error_codes.py --check 112 個|內容一致
空殼掃描 scan_scaffold.py 命中 0 處
假資料掃描 scan_mockdata.py 命中 0 處
假測試掃描 scan_faketest.py 命中 0 處

5. 影響範圍與回改清單——執行狀態

文件/元件 變更 狀態
SDD-006 §3.3 類別位 4 擴充 + 新增 §3.3.1;§3.4 新增 5 碼;§8 修訂記錄 ✅ 升 1.6
CSP-003 §2 類別位 4 擴充 + 新增 §2.1;HTTP 狀態對應表補 2 列;§8 修訂記錄 ✅ 升 1.3
CSP-003 §7 補述請求層例外之訊息不得洩漏內部實作 ✅ 已含於 §2.1 之實作歸屬說明
common-spring GlobalExceptionHandler 新增 5 個 handler(+補 collectFieldDetails 之 HandlerMethodValidationException 分支);build.gradle.kts 補 2 個 compileOnly ✅ 已完成
common-spring 測試 GlobalExceptionHandlerTest 新增 @Nested 請求層 10 項 ✅ 8 → 18
ErrorCodes.java 由 gen_error_codes.py 重生(不手改) ✅ 107 → 112 碼
rewards-service RewardController/RedemptionController 標頭回歸 required = true;Requests.requireChannel() 刪除;AccrualRequest javadoc 更正 ✅ 已完成
SCS-010 §10.4 #15 移除繞道敘述,改記「平台缺口已由 CR-004 修正」 ✅ 升 1.4
SCS-018(WebUI) 前端錯誤處理:確認 400/404/405 之文案與重試策略 ⚪ 未做——見下方註
TRP-001 補對應測試案例 🟡 單元測試已補;TRP-001 之登錄待補

§5 註(誠實揭露):
① SCS-018(WebUI)之前端錯誤文案與重試策略尚未處理——本輪聚焦後端平台層;
前端目前對 400/404/405 之顯示沿用既有通用錯誤處理,未逐一驗證文案。
② 17 份契約之 400/404/405 宣告未逐一核對;已確認 rewards-service.yaml
不列 CC-RWD-4000,故本次改動不影響該契約。
③ TRP-001 之測試案例登錄尚未補;本次證據以 common-spring 單元測試(18 項)
與兩服務端到端實測(各 6 項)為準。


6. 核定結果(原「待核定事項」)

# 待決 核定 處置
1 是否同意新增 §3.1 之 5 個錯誤碼? ✅ 同意,細節授權開發團隊安排 已新增 5 碼;編號依 §3.1「實作定案」調整(4050 取代提案之 4003、4000 保留未用)
2 是否同意 §3.2 之類別位 4 語意擴充? ✅ 同意 CSP-003 §2/§2.1、SDD-006 §3.3/§3.3.1 已同步擴充為「請求與資源」
3 修正後是否一併移除 rewards-service 之 Bean Validation 繞道? ✅ 移除 標頭回歸 required = true;Requests.requireChannel() 刪除。服務層檢核保留(理由見 §6.1)
4 是否於本輪(Sprint 4)執行,或排入下一輪? ✅ 本輪執行 已完成並實測通過

核定前之原則「在核定前不進行任何變更」已解除;本 CR 之變更自 2026-09-19 起生效。

6.1 關於第 3 項「移除繞道」之範圍界定(實作時查證後之更正)

原 CR 之敘述「改回標準 Bean Validation 作法」在實作前經查證後須限縮,理由三點:

# 查證發現 影響
1 (已於 §6.2 更正) 原記「Bean Validation 於全專案 9/9 服務皆 0 使用」——該敘述有誤。實為 8/17 服務採控制器層 Bean Validation,rewards-service 為唯一採應用層檢核者 「改回 Bean Validation」不再是「回歸慣例」,而是與其他 8 服務對齊;性質改變,故不擅自變更,改列待裁定(見 §6.2)
2 AccrualServiceTest 直接呼叫應用服務(不經 HTTP),並斷言非法輸入須拋 CC-RWD-4000 控制器層之 Bean Validation 攔不到非 HTTP 入口;若移除服務層檢核,該測試會失敗且非 HTTP 入口失去防護
3 Requests.requireIdempotencyKey 之**長度規則(8–120)**屬業務規則,非格式檢核 業務規則本就應在應用層,不屬「框架該做的事」

因此本輪移除的是「真繞道」——即 @RequestHeader(required = false) + 手動 requireChannel:
框架本可處理存在性檢核,服務卻自行重做。服務層之檢核保留,其存在理由已改寫為
經證據支持之兩點(保護非 HTTP 入口/錯誤碼精確),並於 Requests 之 javadoc
標明「⚠️ 歷史(已失效之理由)」以免後人誤解。

此為對原 CR 敘述之更正,依「發現矛盾要講」原則明列於此,不掩飾。

6.2 更正:rewards-service 之 Bean Validation 敘述(我方的判斷錯誤)

錯誤內容:CR-004 1.0 之 §3.4 #1 與 §6.1 原記
「Bean Validation 於全專案 9/9 服務皆 0 使用,屬專案慣例而非 rewards-service 獨有繞道」。
並依此寫入 Requests.java 之 javadoc(列為定案理由之第 3 點)與 SCS-010 §10.4 #15.1。

複查結果(2026-09-19,全專案逐服務掃描):

服務 jakarta.validation.constraints @Valid 驗證層次
authorization-service 1 1 控制器層
card-service 1 1 控制器層
installment-service 2 1 控制器層
limit-service 2 2 控制器層
party-service 2 2 控制器層
payment-service 3 2 控制器層
posting-service 2 1 控制器層
statement-service 1 1 控制器層
rewards-service 0 0 應用層(Requests)
其餘 8 服務 0 0 尚未實作

正確結論:8 個服務採控制器層 Bean Validation(@Valid @RequestBody + DTO 註解,
且應用層無對應檢核——實查 party-service/limit-service 確認);
rewards-service 為唯一採應用層檢核者。原敘述「與事實相反」。

根因:以失效之搜尋樣式盤點(macOS BSD grep 之 \| 交替不生效,
見 CSP-001 之工具陷阱紀錄),致命中 0 筆而誤判為「全專案皆無使用」。
教訓:以「0 命中」作為「不存在」之證據前,必須先用已知存在之樣本驗證搜尋樣式本身有效。

處置:

  1. Requests.java javadoc 之第 3 點(專案慣例)刪除,改列「本服務與其他服務之驗證層次不同」,
    並加註「⚠️ 亦已更正之錯誤敘述」小節,載明原敘述有誤及根因。
  2. SCS-010 §10.4 #15.1 同步更正。
  3. 新增待裁定項(不擅自變更已通過測試之行為):
# 待決 選項 影響
1 rewards-service 之驗證層次是否對齊其他 8 服務? ① 維持應用層(現狀)② 於控制器補 Bean Validation(與其他 8 服務一致) ① 風格不一致,但保護所有入口、錯誤碼精確
② 風格一致,但規則重複(控制器+應用層各一份)或須放寬 AccrualServiceTest 斷言

本 CR 不代為決定——此屬設計取捨,依「設計決策由 BEAM PMO 拍板」原則待裁定。
已記入 HANDOVER §📊 §3.C。


7. 修訂記錄

版本 日期 修訂者 說明
1.0 2026-09-19 開發團隊 初版。立 CR(BEAM PMO 指示),載明實測證據、根因、建議修法與待核定事項
1.1 2026-09-19 開發團隊 核定與執行完成。① 狀態改「已核定/已執行」;4 項待決全數核准(§6)。② §3.1 新增「實作定案」對照表,載明與原提案之 3 項差異及理由(後兩碼對齊 HTTP 狀態碼;4000 保留未用)。③ §3.2 記裁定結果與落地位置(CSP-003 1.3/SDD-006 1.6)。④ §3.3 記實作結果(2→7 handler,涵蓋 9 種例外型別);新增 §3.3.1 載明實作時發現並補強之 4 項缺口(含 HandlerMethodValidationException 明細缺失、NoResourceFoundException 之 jar 歸屬與建構子語意、emptyResult() 不可作 fixture)。⑤ 新增 §3.5 逐項說明 3 個未涵蓋例外之理由(不為「可能用到」預立碼)。⑥ §3.4/§5 改為「執行狀態」表並加註 3 項誠實揭露(SCS-018 前端文案、17 契約逐一核對、TRP-001 登錄尚未補)。⑦ 新增 §4.1 實測證據——兩服務同服務前後對比(各 6 情境)、日誌分級(0 ERROR)、回應主體不洩漏、5 項自動化驗證。⑧ 新增 §6.1 更正原「改回 Bean Validation」之敘述(範圍限縮為「移除真繞道」),理由三點。⑨ 新增 §6.2 更正我方之判斷錯誤——原記「Bean Validation 全專案 9/9 服務皆 0 使用」與事實相反(實為 8/17 服務採控制器層 Bean Validation,rewards-service 為唯一採應用層檢核者);根因為以失效之搜尋樣式(BSD grep 之 |)盤點致 0 命中而誤判。已同步更正 Requests.java javadoc 與 SCS-010 §10.4 #15.1,並將「是否對齊」列為待裁定項(不擅自變更已通過測試之行為)
    (1-1/1)