Project

General

Profile

CR #201 » SCS-020_監控與觀測機制程式設計規格.md

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

 

SCS-020 監控與觀測機制(Observability)程式設計規格(跨域)

項目 內容
文件編號 SCS-020
版本 1.11
狀態 已核定(規格先行·跨域)
對應需求 BRD-003:監控機制(21 項)、System Monitoring(11 項)、Monitoring(8 項)——均對應 SAD-006 §5、CSP-004
技術棧 OpenTelemetry → Jaeger(本地)/Prometheus + Grafana + Loki(參 CSP-006)
關聯文件 BRD-003、SAD-006 §5、CSP-004、CSP-001(例外/追蹤 common-spring)

修訂記錄

版本 日期 異動
1.11 2026-10-01 新增 §9.12 分流比例管理端點(api-gateway)——解鎖 SDD-007 §5.1.2.3 桶 C 之「分流比例管理」(/system/split-ratio,原記「僅 actuator」)。選頁理由:SAD-004 §2.4 之分流機制(CycleSplitDecision/CycleSplitGlobalFilter/CycleSplitProperties,POC-13 已驗收)完整存在,缺的只是管理面——不需新領域模型、不會造空殼,且「灰度放量與回退」為遷移期最高頻維運操作。規格先行:立本節 → 實作 → SDD-007 §5.4.30 逐鈕定義 → 頁面。設計要點:定點行替換(僅改 enabled:/percentage: 兩行,保留註解)+ ContextRefresher.refresh()(不重啟)+ 回讀驗證(effectiveMatchesRequest,不以請求值冒充生效值)+ 失敗不靜默(CC-PLT-5000)。⚠️ 未竟:角色級授權/稽核落庫/回退歷程/多實例組態同步(見 §9.12.5)
1.10 2026-10-01 BEAM PMO 2026-10-01 裁定之兩項執行(GOV-007):① §9.5 #3 更正——CorrelationIdFilter 之「待裁定/未擅自註冊」係過期記載;實已由 CorrelationIdAutoConfiguration(登錄 AutoConfiguration.imports)以 FilterRegistrationBean 註冊(BEAM PMO 2026-09-21 18:57 裁定「保留並註冊」),並顯式指定 ORDER(LOWEST_PRECEDENCE - 100)確保晚於 OTel 觀測範圍。② §9.5 #5 實作——19 個 application.yml 均設 logging.logback.rollingpolicy.total-size-cap: 5GB(BEAM PMO 裁定「限制 5GB」);併更正原建議之屬性名(logging.file.total-size-cap 不存在;正解為 logging.logback.rollingpolicy.total-size-cap,實查 metadata 確認)。⚠️ per-service 語意(本地 19 模組共用目錄)已於 §9.5 #5 誠實揭露
1.9 2026-10-01 新增 §9.11.6 業務 KPI 區塊(BffDashboard.business,CR-026):/dashboard 由「系統營運面」擴充為「系統營運面 + 業務 KPI」。business 含 4 子塊(cards/applications/authorizations/disputes),各 oneOf schema-or-null,獨立降級(任一統計取不到 → 該子塊為 null,不以 0 充數);由 BusinessStatsService 並行 4 路 CompletableFuture + DelegatingSecurityContextExecutor 聚合,逾時 STATS_TIMEOUT_MS=5000
1.8 2026-10-01 新增 §9.11 營運總覽聚合端點(bff-web)——解鎖 SDD-007 §5.1.2.3 桶 C 之「營運總覽」(/dashboard)。規格先行:立本節 → 改 tools/gen_openapi.py → 重生契約 → 實作 → SDD-007 §5.4.28 逐鈕定義 → 頁面。★ 回訪查證(關鍵):掃全數 17 服務契約,皆無「統計/摘要」端點(僅 CRUD 與列表)→ 業務 KPI 無法由既有端點推得 → 故明訂範圍界定(§9.11.1):本版為系統營運面(服務健康/批次/端點連線摘要,bff-web 自足),業務 KPI 屬第二階段(需各服務統計端點,另立 CR)。設計要點:複用既有三個聚合服務(不重複探測)、並行執行、區塊降級(失敗區塊為 null,不以 0 充數)、不快取、fail-closed
1.7 2026-09-30 新增 §9.8.6「實作註記:分層與目標埠」(HANDOVER #A-105)。⚠️ HealthAggregationService(application)原直接注入 infrastructure.config.BffHealthProperties,違反 CSP-002 §6.3(LayeringTest 於 HEAD 即紅)→ 採「應用層立埠」:application/HealthTargetsPort + infrastructure/config/HealthTargetsConfigImpl(沿用 AccountOverviewPort 慣例);併補 3 個單測(JDK 內建 HttpServer 造真目標,測真實網路行為而非 stub)→ :platform:bff-web:test 12 測 0 失敗、全綠(原 9 測 1 失敗)。⚠️ 併載:空 targets 之語意不得放寬(回空 Map=「未組態」→ 必須 DOWN),埠之實作原樣轉發即為確保不被靜默補值
1.6 2026-09-30 §9.9.1 更正 + 四服務列表端點規格先行(#A-104)。⚠️ 原載「四個既有端點,皆已實作並真機驗證」經實測不成立——四個路徑皆只有 POST,GET 回 405/404/404/404。故先行新增四個 GET 列表端點:GET /statement-runs/GET /clearing/runs/GET /migration-jobs/GET /report-jobs(各 ?limit=N,預設 20/上限 100),並於 SCS-011 §6.1/SCS-013 §6.1/SCS-015 §6.1/SCS-017 §6.1 各立規格。時間錨點:clearing 無 startedAt 欄位 → 本聚合取值順序為 startedAt → createdAt,不臆造。§9.9.3 補 limit 參數定義(每服務上限,預設 10/上限 50)。四服務 DTO 刻意不統一——以別名鏈吸收差異,避免各服務背負非其職責之欄位
1.5 2026-09-30 新增 §9.9 批次作業監控 + §9.10 端點與連線管理(bff-web)——解鎖桶 C 兩頁(SDD-007 §5.4.26/§5.4.27)。⚠️ 併補記:§9.9/§9.10 之規格與契約已於同日先行完成(契約 81 端點、0 錯誤),惟當時未登修訂記錄,本版一併補登。§9.10.2 之原設計經實測修正:gateway actuator endpoint 於本技術棧版本(Spring Cloud Gateway 5.0.3)未被註冊(/actuator 索引僅 7 個,加入 exposure.include 亦無效)→ 改採 api-gateway 自供唯讀端點 GET /api/v1/gateway/routes(#A-103)。實作要點:讀 RouteDefinitionLocator(非 RouteLocator——後者已解析為物件、無法還原原文);原文還原依 getArgs() 之 LinkedHashMap 插入順序(javap 確認);維持宣告順序不重排;追蹤碼於 controller 方法進入時取(避免 Reactor 脈絡傳遞之依賴)。真機:bff 之 routesAvailable=true(修正前 false)、17 筆路由、service 由 routeId 正確推導;連線維度 UP 7/17 不受影響。§9.10.5 補 filters 未由 bff 呈現(契約未含,不逕行加欄位)與不自訂錯誤碼(CC-GW-* 無對應語意)
1.4 2026-09-29 新增 §9.8 健康聚合端點(bff-web)——解鎖 SDD-007 §5.1.2.3 桶 C 之「健康檢查與監控」頁。規格先行:立本節 → 改 tools/gen_openapi.py → 重生契約(78 端點、0 錯誤)→ 實作 → SDD-007 §5.4.24 逐鈕定義 → 頁面。★ 併載兩個實測揭露之陷阱:①@Value("#{...}") 讀 Map 靜默失敗(property key 含 - 非合法 SpEL)→ 須用 @ConfigurationProperties;②並行探測必須傳遞 SecurityContext(token-relay 模式下 RelayingOutboundTokenProvider 僅適用同執行緒)→ 須用 DelegatingSecurityContextExecutor,否則全部服務探測失敗
1.0 2026-09-19 初版核定(規格先行)
1.2 2026-09-20 WorkBuddy AI
1.3 2026-09-20 WorkBuddy AI
1.4 2026-09-21 WorkBuddy AI
1.1 2026-09-19 新增 §9 實作機制與實測證據(W-4 接線):三支柱實測結果、四項實作時發現之缺陷與修正、五項已知限制

1. 對應需求(NFR)

NFR 類別 數量 來源 驗收
監控機制 21 SAD-006 §5、CSP-004 監控指標與告警規則驗證、儀表板檢視
System Monitoring 11 SAD-006 §5 監控覆蓋率檢核
Monitoring 8 SAD-006 §5 監控項目逐項驗證

2. 觀測架構

應用(Spring Boot)──OTel SDK──→ Jaeger(Trace,本地)
                            └──→ Prometheus(Metrics)→ Grafana(Dashboard)
                            └──→ Loki(Logs,結構化)
  • Trace:統一以 OTel W3C Trace Context 為跨系統唯一識別(CSP-004 §6,BEAM PMO 2026-09-20 裁定),
    由 common-spring 之 TraceContext 讀取(取值順序:OTel span → MDC → 臨時值)。

    ⚠️ 已廢除第二套 correlation_id——原 v1.0 設計另設 correlation_id 貫穿日誌,
    實測導致 API 回應與日誌之 ID 互不相通(10b8a469… ≠ 894ff808…),查測時無法對照。

    ⚠️ CorrelationIdFilter 目前未註冊(AutoConfiguration.imports 未登錄、類別亦無 @Component),
    其職責已被 OTel 取代;註冊或退役待 BEAM PMO 裁定(詳 §9.5 #3),故本節不將其列為現行機制。

    ℹ️ 勿與 DomainEvent.correlationId() 混淆——那是領域事件信封欄位
    (CSP-007、SAD-001 AP-05、SDD-006 §4.1),用於事件鏈之業務關聯,仍然存在且有效;
    被廢除的是「日誌/追蹤」層的那一個。
  • Metrics:RED(Request/Error/Duration)+ USE(Utilization/Saturation/Errors),經 Micrometer 暴露。

3. 指標(Metrics)

指標 說明 來源
授權成功率/失敗率 業務指標 authorization-service
端點 P99 延遲 效能指標 所有 REST 端點(Micrometer)
JVM/連線池飽和度 USE common-spring
Kafka 消費滯後 管線健康 common-event
批次執行狀態 作業指標 common-batch

4. 追蹤(Tracing)

  • 所有 inbound/outbound 自動埋點(common-spring TraceContext);資料庫/Kafka 跨度自動化。
  • OTel traceId 貫穿日誌(ECS 輸出為頂層駝峰 traceId/spanId,見 CSP-004 §3.1),
    使 API 回應之 traceId 與日誌之 traceId 為同一值,便於跨服務排查。

    ⚠️ 原設計之 correlation_id 已廢除(CSP-004 §6),理由見 §2。

5. 日誌與稽核留痕

  • 結構化 JSON 日誌 → Loki。
  • 稽核留痕:關鍵變更(管制碼設定、額度調整、爭議)除業務表(block_code_history 等)外,亦落日誌,符合銀行審計要求(common-security 加密/遮蔽)。

6. 告警(Alerting)

  • Prometheus Alertmanager → Grafana 告警;依 BRD-003 監控機制 21 項定義規則(延遲門檻、錯誤率、飽和度)。
  • 分級(P1/P2/P3)與值班(SRE)路徑。

7. 測試案例(對應 TRP-001)

  • 監控覆蓋率檢核(各服務端點/批次指標齊全)
  • 追蹤貫穿測試(gateway→bff→服務→Kafka→下游 traceId 連續;correlation_id 已廢除,見 §2)
  • 告警規則觸發測試(注入延遲/錯誤驗證告警)

8. 已知限制(誠實揭露)

  • 具體儀表板版面與告警閾值(與 BRD-003 21 項對應)待 SRE 與 SAD-006 §5 凍結。
  • 本地 Jaeger 與生產 Prometheus/Grafana/Loki 之佈署差異(SAD-006 部署章節)待確認。
  • 稽核日誌之保留期與合規(如金管會要求)待法遵確認。

9. 實作機制與實測證據(W-4 接線,2026-09-19)

目的:將 §2 之觀測架構由「文件」落為「可驗證之實物」。
本節所有數據皆為實測,非設計推估。

9.1 三支柱實測結果

支柱 路徑 實測證據 判定
Metrics Prometheus 直接 scrape host.docker.internal:<8081–8098>/actuator/prometheus 4 個服務(8084/8088/8089/8091)target UP;jvm_memory_used_bytes 回傳 24 個序列,含 heap/nonheap 實際值 ✅ 通過
Traces Spring Boot → OTLP/HTTP → OTel Collector → Jaeger Jaeger /api/services 列出 party-service;查得 5 筆 trace,span 含 http get /api/v1/parties/{partyId}(8,027µs) ✅ 通過
Logs Spring Boot 結構化 ECS JSON 寫檔 → Collector filelog → OTLP → Loki Loki 查得實際日誌,含 message/traceId/spanId/log_level/deployment_environment=local ✅ 通過

Metrics 採「直接 scrape」而非「OTLP push」之理由:
micrometer-registry-prometheus 已於各服務就緒、/actuator/prometheus 已暴露,
直接 scrape 零新增依賴且已驗證可用。OTLP push 需另加 micrometer-registry-otlp;
Collector 之 metrics pipeline 已保留,日後可切換。

9.2 實作時發現之缺陷(原組態從未成功啟動)

⚠️ 重要更正:先前認知為「容器已在,只差接線」。實測發現
cc-otelcol 自建立以來從未成功啟動(無限重啟),組態有三處致命缺陷。

# 缺陷 實測症狀 修正
1 loki exporter 已自 contrib 移除 unknown type: "loki" for id: "loki",Collector 啟動即失敗 改走 Loki 3.x 原生 OTLP 攝取端點 /otlp/v1/logs(實測回 422 表示端點存在;/otlp/v1/metrics 回 404=不存在)
2 replace_pattern 用 Go RE2,不支援 lookahead (?= invalid or unsupported Perl syntax: (?=,啟動失敗 改為 replace_pattern(..., "^.+$", "****") 完整遮蔽。亦較符合 CSP-004 §6「禁止在 span 記錄敏感 payload」
3 prometheus.yml 之 target 寫容器 DNS 名(party-service:8080) 17 個 target 全數 DOWN(服務跑在主機,非 compose 內) 改為 host.docker.internal:<8081–8098>(實測解析為 192.168.65.254)

附帶修正:otlp→otlp_grpc、otlphttp→otlp_http 之 deprecated 別名;
Collector 健康埠 13133 原未映射至 compose,本機無法探測(易誤判為「沒起來」)。

9.3 Spring Boot 4 之 OTLP 陷阱(與 Flyway 同源)

命名空間不一致——誤用不報錯,只會靜默不匯出:

信號 正確屬性 所需依賴
Traces management.opentelemetry.tracing.export.otlp.endpoint spring-boot-starter-opentelemetry
Metrics management.otlp.metrics.export.url micrometer-registry-otlp
Logs management.opentelemetry.logging.export.otlp.endpoint 尚需第三方 log bridge

原寫於各服務之手動組合(micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp)
不會觸發 management.opentelemetry.tracing.export.otlp.* 之自動配置。已改用 starter,
並確認其傳遞引入上述兩者(故原手動宣告為冗餘,已移除)。

9.4 日誌為何不走「應用直接 OTLP 推送」

Spring Boot 4.1 不提供將 Logback 日誌橋接至 SdkLoggerProvider 的內建機制
(官方文件:「Spring Boot doesn't support bridging logs to this bean out of the box」)。
唯一官方推薦之 opentelemetry-logback-appender-1.0,所有版本皆為 -alpha
(實測 Maven Central,最新 2.31.1-alpha)——銀行案不應引入 alpha 相依。

故採:Spring Boot 內建結構化日誌(logging.structured.format.file=ecs,無須第三方 encoder)
寫檔 → Collector filelog receiver 尾隨讀取 → OTLP → Loki。零第三方相依。

9.6 推廣至全模組(HANDOVER #C-23,2026-09-20)

背景:§9.5 #4 記載「僅 party-service 接線(觀測 1/17)」。本節為該項之解除。

根因(實測,非推測):其餘 18 個模組(16 個服務 + api-gateway + bff-web)
之 build.gradle.kts 採手動組合——

implementation("io.micrometer:micrometer-tracing-bridge-otel")
implementation("io.opentelemetry:opentelemetry-exporter-otlp")

此組合不會觸發 Spring Boot 4 之 management.opentelemetry.tracing.export.otlp.* 自動配置
(見 §9.3),故依賴看似齊備、實則從未匯出任何 trace,且不拋出任何錯誤。
與 §9.2 之 Flyway/loki exporter 同屬「靜默不做」型缺陷。

修正範圍

# 修正 數量
① 依賴改為 org.springframework.boot:spring-boot-starter-opentelemetry(移除原手動組合) 18 個模組(party-service 原已正確)
② 組態補 management.opentelemetry.tracing.export.otlp.endpoint + logging.structured.format.file=ecs + logging.file.name 18 個模組
③ tools/gen_service_skeleton.py 模板同步修正(service/gateway/bff 3 處) 避免新模組重蹈覆轍
④ prometheus.yml 新增 cardcore-platform job(api-gateway:8100/bff-web:8101) 原僅 scrape 17 個領域服務,平台層為監控缺口

① 與 ② 皆同步修改「既有檔案」與「產生器模板」:產生器只寫入不存在的檔案,
故僅改模板無法修復既有 18 個模組,兩邊皆須動手。

逐模組實測(逐一啟動,非抽樣)

模組 埠 /actuator/health /actuator/prometheus Jaeger trace 結構化日誌 JSON Loki 行數
authorization-service 8081 200 200 ≥5 ✅ 48
card-service 8082 200 200 ≥5 ✅ 49
limit-service 8083 200 200 ≥5 ✅ 48
party-service 8084 200 200 ≥5 ✅ 112
product-service 8085 200 200 ≥5 ✅ 48
pricing-service 8086 200 200 ≥5 ✅ 48
posting-service 8087 200 200 ≥5 ✅ 48
payment-service 8088 200 200 ≥5 ✅ 49
installment-service 8089 200 200 ≥5 ✅ 48
rewards-service 8091 200 200 ≥5 ✅ 48
statement-service 8092 200 200 ≥5 ✅ 48
dispute-service 8093 200 200 ≥5 ✅ 48
clearing-service 8094 200 200 ≥5 ✅ 48
merchant-service 8095 200 200 ≥5 ✅ 49
migration-service 8096 200 200 ≥5 ✅ 48
notification-service 8097 200 200 ≥5 ✅ 48
report-service 8098 200 200 ≥5 ✅ 48
api-gateway 8100 200 200 ≥5 ✅ 26
bff-web 8101 200 200 ≥5 ✅ 17

上表「Jaeger trace」為該服務當次探測回傳之 trace 筆數(查詢上限 5,故 ≥5 即代表有匯出)。

三支柱之最終驗證

支柱 驗證方式 結果
Traces Jaeger /api/services 19 個模組全數在列(+jaeger-all-in-one = 20)
Logs Loki 依 service_name_extracted 查詢 19/19 皆有日誌(48–112 行;gateway 26/bff 17 行)
Metrics Prometheus /api/v1/targets 21 個 target:領域服務 17 個、平台層 2 個、Collector、Prometheus 自身。
領域服務 scrape UP 17/17(分兩批實測:11 + 6),平台層 UP 2/2

實測時之三個踩坑(皆為「不報錯」型)

  1. 取樣率:預設 TRACING_SAMPLING=0.1(10%),單次請求有 90% 機率不被取樣,
    驗證會隨機失敗。實測時須設為 1.0。
  2. Loki 查詢語法:service_name_extracted 係 Loki 3.x 自動萃取之
    structured metadata,不能置於 stream selector {{}} 內——查得到 0 筆且不報錯;
    須寫為 {{service_name="unknown_service"}} | service_name_extracted="<svc>"。
    另 /loki/api/v1/query 為 instant query,不支援 log query(須用 /query_range)。
  3. 併發啟動限制:19 個 JVM 同時啟動時,6 個服務因 HikariPool
    Connection is not available, request timed out after 3003ms 於 Flyway 初始化階段失敗。
    屬本機資源限制(4 核/Oracle sessions 上限 322),與本次監控接線無關——
    該 6 個服務於逐一啟動與後續分批啟動時皆正常,且皆已取得 trace/日誌/scrape 證據。

9.7 IAM(Keycloak)納入監控(HANDOVER #C-35,2026-09-20)

背景:§9.6 完成 19 個應用模組之接線後,身分認證服務(Keycloak)仍完全無指標,
且其健康端點由主機探測一律 404。

查明結果(實測 + 官方文件):健康端點本來就正常——
Keycloak 26 之 /health*、/metrics 位於管理埠 9000(官方文件:「exposed on the
management port 9000 by default」),不是主埠 8180。原 compose 未映射 9000,
故由主機探測皆 404,易被誤判為「端點壞掉」。
容器內實測 9000/health/ready → 200 {"status":"UP"}。

# 修正 實測結果
① docker-compose.yml 映射 9000:9000 /health//health/ready//health/live//health/started 皆 200;內容含 Keycloak database connections async health check: UP
② 補 KC_METRICS_ENABLED=true /metrics 200;約 930 個序列(vendor 673/jvm 61/cache 48/http 40…)。
註:Database 健康檢查項目需同時啟用指標才會出現(官方文件)
③ prometheus.yml 新增 keycloak job(target host.docker.internal:9000,metrics_path=/metrics) up{job="keycloak"}=1、序列 930;target 總數 21 → 22

回歸驗證:容器重建後,權杖簽發仍正常
(POST /realms/cardcore/protocol/openid-connect/token → demo.holder、角色 cardcore-customer)。
另註:cardcore-services 為機密客戶端,password grant 須帶 client_secret
(僅帶 client_id 會回 unauthorized_client)。

⚠️ 資安提醒(未解決,須於上線前處理):管理埠不應對外暴露。
本機開發可接受;SIT 以上應僅允許監控網段存取,或改以容器內 HEALTHCHECK
於容器內探測({ printf 'HEAD /health/ready HTTP/1.0\r\n\r\n' >&0; grep 'HTTP/1.0 200'; } 0<>/dev/tcp/localhost/9000),
即無須映射埠。

9.8 健康聚合端點(bff-web)與作業台健康頁(2026-09-29)

緣由:SDD-007 §5.1.2.3 之桶 C「健康檢查與監控」(/system/health)原記
「須先於 bff-web 新增健康聚合端點方可實作」——本節即為該端點之規格。
依「規格先行」:先立本節 → 改 tools/gen_openapi.py → 重生契約 → 實作 → SDD-007 §5.4.24 逐鈕定義 → 頁面。

9.8.1 為何前端不能直接呼叫各服務 /actuator/health

障礙 說明
CORS 各服務未對瀏覽器來源開放
gateway 未代理 actuator 前端無法經閘道取得
bff-web 原僅 1 支聚合端點 無 /health 聚合

→ 故須由 bff-web 代為探測並聚合。

9.8.2 端點規格

GET /api/v1/bff/health(回應 BffHealth,200)

欄位 型別 說明
overall UP/DEGRADED/DOWN UP=全部 UP;DEGRADED=部分 DOWN;DOWN=全部 DOWN
checkedAt date-time 本次檢查時刻
services[] array 各服務:service/status(UP/DOWN)/latencyMs/error

9.8.3 行為要求

  1. 並行探測各服務之 /actuator/health;單一逾時不阻塞整體(逾時 2 秒)。
  2. 降級呈現:失敗者標 DOWN 並帶 error,不使整體失敗——
    與 BffSection 之「單一下游失敗不拖垮整體」同一政策(SAD-004 §2.2)。
  3. 不快取——健康狀態須即時;快取會使畫面顯示過期狀態(比不顯示更危險)。
  4. 失敗即如實呈現,不得以樂觀預設值掩蓋(CSP-001 §8 B2)。

9.8.4 ⚠️ 定位說明(回應 SDD-007 §5.1.2.3 之「本頁定位須先釐清」)

本端點與其頁面之定位為 作業台——維運人員之即時總覽(哪些服務 UP/DOWN)。

不取代 Prometheus/Grafana 之長期指標與告警——後者仍為 NFR Monitoring 之正式方案。
→ 頁面須明載此定位,避免使用者誤解為「監控看板」(瀏覽器畫面無法呈現歷史趨勢與告警規則)。

9.8.5 未竟

  • 端點涵蓋之服務清單以 cardcore.services.* 之組態為準;未組態者不列入(避免硬編 17 個服務)。
  • 本端點不聚合 JVM/連線池等細部指標——那屬 /actuator/prometheus,仍由 Prometheus 蒐集。

9.8.6 實作註記:分層與目標埠(2026-09-30 修正,HANDOVER #A-105)

緣由:HealthAggregationService(application)原直接注入
infrastructure.config.BffHealthProperties——違反 CSP-002 §6.3 之依賴方向
(interfaces → application → domain ← infrastructure),
LayeringTest.applicationMustNotDependOnInfrastructure 於 HEAD 即紅。

修正採「應用層立埠」(非把屬性類別搬進 application):

角色 類別 說明
埠(application) HealthTargetsPort targets()/timeoutMs();沿用 AccountOverviewPort 既有慣例
實作(infrastructure) HealthTargetsConfigImpl 適配 @ConfigurationProperties;原樣轉發、不補預設值

為何不搬類別:屬性類別本質是基礎設施之組態繫結,搬進 application 只是把違規搬家,
且讓應用層背負 Spring 繫結細節。立埠後應用層只依賴抽象,得以手寫 fake 測試(無須啟動 Spring)。

⚠️ 空 targets 之語意不得放寬:回空 Map 代表「未組態」,
呼叫端必須回報 DOWN(§9.8.3 #4),不得以樂觀預設值掩蓋。
埠之實作原樣轉發,即為確保此語意不被靜默補值。

測試(HealthAggregationServiceTest,3 例):以 JDK 內建 HttpServer 造「活的目標」、
以連不上的埠造「死的目標」——測真實網路行為(逾時、狀態碼判定、逾時/連線失敗之分流),
而非 stub 假造 HTTP 結果(後者測的是 stub,不是探測邏輯)。三例:空 targets→DOWN/
一活一死→DEGRADED(UP 帶 latencyMs、DOWN 如實帶原因)/全死→DOWN。


9.9 批次作業監控聚合端點(bff-web)與作業台批次頁(2026-09-30)

緣由:SDD-007 §5.1.2.3 之桶 C「批次作業監控」(/system/batch)——
本節為其後端端點之規格。與 §9.8 之健康聚合不同:本節聚合的是各服務的批次執行紀錄。

⚠️ 前置變更(2026-09-30 補):聚合之前提是四服務各有「列出批次」端點——
實測四者皆無(僅有觸發與單筆查詢)→ 故先行新增四個 GET 列表端點(見 §9.9.1),
規格分載於 SCS-011 §6.1/SCS-013 §6.1/SCS-015 §6.1/SCS-017 §6.1。

9.9.1 資料來源(⚠️ 原載「四個既有端點」與事實不符——已更正)

⚠️ 實測揭露(2026-09-30,#A-104):本節原載「四個既有端點,皆已實作並真機驗證」——
經實測不成立:四個路徑皆只有 POST(觸發),GET 實測分別回
405/404/404/404。故本規格先行新增四個 GET 列表端點,bff-web 改接之。

服務 列表端點(本規格新增) 規格出處 狀態值域 時間錨點
statement-service GET /api/v1/statement-runs?limit=N SCS-011 §6.1 GENERATING/GENERATED/FAILED startedAt
clearing-service GET /api/v1/clearing/runs?limit=N SCS-013 §6.1 RUNNING/MATCHED/DIFFERENCE/FAILED createdAt
migration-service GET /api/v1/migration-jobs?limit=N SCS-015 §6.1 RUNNING/COMPLETED/FAILED startedAt
report-service GET /api/v1/report-jobs?limit=N SCS-017 §6.1 QUEUED/RUNNING/COMPLETED/FAILED startedAt

⚠️ clearing 無 startedAt 欄位(CLEARING_RUNS 僅有 CREATED_AT/UPDATED_AT)——
故本聚合之時間錨點取值順序為 startedAt → createdAt;皆取不到者留空,
不以當前時間或 0 填充
(§9.9.4 #5 不臆造原則)。
⚠️ 四服務之列表回應形狀各異(CycleRunView[]/ClearingRunSummary[]/
MigrationJobSummary[]/ReportJobSummary[])——本服務以 firstText/firstLong 之
別名鏈吸收差異(如 jobId|runId),不強求四服務統一 DTO(那會使各服務背負
非其職責之欄位)。

9.9.2 ★ 統一狀態分組(本節核心)

四服務之狀態值域各不相同——若畫面直接顯示原始狀態,使用者無法一眼看出「哪些批次要注意」。
故定義統一分組(原始狀態一律如實保留,分組僅為呈現用):

分組 涵蓋之原始狀態 語意
PENDING QUEUED 已排入、尚未開始
RUNNING GENERATING/RUNNING 執行中
SUCCEEDED GENERATED/MATCHED/COMPLETED 成功完成
WARNING DIFFERENCE 完成但有差異(對帳不平——非失敗,但須人工確認)
FAILED FAILED 失敗

⚠️ DIFFERENCE 不可歸為 SUCCEEDED:對帳有差異之批次帳務上不等於成功,
歸為成功會使差異被忽略(假綠)。
⚠️ 分組不得取代原始狀態——回應中兩者並存,畫面以原始狀態為主、分組為輔(篩選/排序用)。

9.9.3 端點規格

GET /api/v1/bff/batch-jobs?limit=N(回應 BffBatchJobs,200)

參數 型別 說明
limit integer(可選) 每服務回傳筆數上限(轉傳至四服務之 limit)。預設 10,上限 50
欄位 說明
checkedAt 本次查詢時刻
jobs[] 統一清單:jobType/service/jobId/status(原始)/statusGroup/processedCount/successCount/errorCount/startedAt/finishedAt/detail
unavailable[] 查詢失敗之服務:service/error——如實呈現,不使整體失敗

jobType:STATEMENT_CYCLE/CLEARING_RUN/MIGRATION_JOB/REPORT_JOB。

9.9.4 行為要求

  1. 並行查詢四個服務;單一逾時不阻塞整體(逾時 3 秒——批次查詢較健康檢查重)。
  2. 部分失敗不拖垮整體:失敗之服務列入 unavailable[] 並帶原因,其餘照常呈現。
  3. 合併後依 startedAt 遞減(最近執行者在前)。
  4. 不快取——批次狀態須即時。
  5. 不得臆造:服務未回傳之欄位(如 clearing 無 processedCount)留空,
    不以 0 或推測值填充(CSP-001 §8 B2)。

9.9.5 未竟

  • 本端點僅查詢(監控);不提供觸發或中止批次之功能——那屬各服務之職責與權限範疇。
  • clearing 之 DIFFERENCE 目前僅能呈現差異筆數/金額,無法下鑽至明細(需 clearing 另立端點)。

9.10 端點與連線管理聚合端點(bff-web)與作業台端點頁(2026-09-30)

緣由:SDD-007 §5.1.2.3 之桶 C「端點與連線管理」(/system/endpoints)——
本節為其後端端點之規格。SDD-007 §5.1.1 另載:原型之「網路連線管理(拓樸/主機連線/容錯切換)」
已由本頁涵蓋(判定為重疊)。

9.10.1 本頁之兩個維度

維度 資料來源 說明
端點(路由) api-gateway 之 GET /api/v1/gateway/routes(自訂唯讀端點;原 actuator 版不可用,見 §9.10.2) 各服務之入口路由(routeId/uri/predicates)
連線(狀態) 各服務 /actuator/health(與 §9.8 同源) 該路由之目標服務是否連得上

★ 兩者並列才完整:只看路由不知通不通;只看健康不知入口在哪。

9.10.2 前置變更(gateway)——⚠️ 原設計經實測後修正(2026-09-30)

原擬:於 api-gateway 之 management.endpoints.web.exposure.include 加入 gateway,
以取用 /actuator/gateway/routes。

實測結果(本版本不可用):

檢查 結果
/actuator 索引 僅 7 個 endpoint:prometheus/beans/env/info/health/metrics/refresh——無 gateway
啟動日誌 Exposing 7 endpoints beneath base path '/actuator'
路由屬性之前綴 spring.cloud.gateway.server.**webflux**.routes[...](Spring Cloud Gateway 2025 已改前綴)

→ gateway actuator endpoint 於本版本未被註冊(非 exposure 設定問題——已加入 include 仍無效)。

改採方案:自訂唯讀端點

GET /api/v1/gateway/routes(api-gateway 自身提供)

欄位 說明
id 路由識別碼(如 authorization)
uri 目標 URI
predicates 述詞原文(如 Path=/auth-api/**)
filters 過濾器原文(如 RewritePath=/auth-api/(?<segment>.*), /api/$\{segment})

⚠️ $\{segment} 之反斜線是組態原文,不是筆誤——application.yml 即如此書寫;
Spring 之 RewritePathGatewayFilterFactory 會執行 replacement.replace("$\\", "$")
再交給 String.replaceAll(經 javap -c 確認)。本端點原文透傳、不代為去轉義
——去轉義等於隱藏真實組態,維運反而看不到「實際寫了什麼」。

要求:

  1. 唯讀——不提供新增/修改/刪除路由(避免誤觸造成路由中斷)。
  2. 僅供內部維運(bff-web 聚合用)——不對外;仍受 gateway 之認證保護。
  3. ⚠️ 不得以 management.endpoint.env.show-values=ALWAYS 替代——
    那會暴露所有環境變數(含憑證),安全風險,不可接受。

★ 實作要點(2026-09-30 實作,#A-103):

項目 決定 理由
資料來源 RouteDefinitionLocator(非 RouteLocator) 前者為組態宣告、保留宣告順序且可還原原文;後者已解析為 Predicate/GatewayFilter 物件,無法還原原文(違反「原文透傳」)
原文還原 Name=arg0, arg1(依 getArgs() 之 LinkedHashMap 插入順序串接) _genkey_0、_genkey_1… 之插入順序即組態順序(經 javap 確認欄位為 LinkedHashMap,非 HashMap)
路由順序 維持宣告順序,不重排 與 application.yml 逐條對照(排序會失去對照性)
回應格式 統一封套(CSP-003 §3),data 為路由陣列 全系統一致;bff-web 讀 data
端點路徑 /api/v1/gateway/routes(非 /actuator/**) 自訂端點;與既有路由前綴(/auth-api/** 等)不重疊,不會被述詞攔截
追蹤碼取值 於 controller 方法進入時取(非於 map 內) map 之執行執行緒不保證仍帶 OTel 脈絡(需 Hooks.enableAutomaticContextPropagation()),於請求執行緒取值可確保回應 traceId 與日誌同源(CSP-004 §6)
錯誤處理 不自訂錯誤碼 SDD-006 §3.4 無對應之 CC-GW-* 碼(僅 1001/1002/1003/4290/5001);不得自創碼。且本端點讀記憶體組態、無實際失敗路徑,失敗時由 bff-web 以 routesAvailable=false 降級呈現

★ 真機驗證(2026-09-30,#A-103):

驗證項 結果
GET /api/v1/gateway/routes(SSO 開啟、無 token) 401——證實端點受閘道認證保護(要求 2「仍受認證保護」成立)
GET /api/v1/gateway/routes(SSO 關閉) 200、data 為 17 筆路由、順序=application.yml 宣告順序、predicates/filters 原文正確
GET /api/v1/bff/endpoints 200、routesAvailable=true(修正前為 false)、routesError=null、17 筆路由且 service 由 routeId 正確推導(authorization → authorization-service)
連線維度(未受影響) 17 筆、UP 7/17——與「7 個領域服務在跑」一致(§9.10.4 #1 兩維度獨立成立)

⚠️ 驗證方式之誠實揭露:取 Keycloak token 之指令遭安全審批攔截(逾時未授權),
故上表第 2 列改以暫時關閉 SSO 完成(CARDCORE_SECURITY_ENABLED=false 之 JVM 覆寫,
不動 sso.env 檔);測畢已還原,並確認 sso.sh status 為「同調(兩側皆 true)」。
處置與 #A-102 ⑦ (iii) 相同(截圖期間暫時關閉 SSO)。

教訓:「actuator endpoint 存在」是需實測的假設,非查文件即可斷定;
且 Spring Cloud 版本演進會改變設定前綴與 endpoint 註冊條件。

9.10.3 端點規格

GET /api/v1/bff/endpoints(回應 BffEndpoints,200)

欄位 說明
checkedAt 本次查詢時刻
routes[] routeId/uri/predicates[]/service(自 routeId 對映之服務名)
connections[] service/status(UP/DOWN)/latencyMs/error
routesAvailable gateway 路由是否取得成功(false 時 routes[] 為空並附 routesError)

9.10.4 行為要求

  1. 兩部分獨立:gateway 路由取不到不影響連線狀態之呈現(反之亦然)——
    分開回報,不使整體失敗。
  2. 並行探測連線(逾時 2 秒,同 §9.8);gateway 路由查詢逾時 3 秒。
  3. 不快取。
  4. ⚠️ service 之對映:由 routeId 推導(如 authorization → authorization-service);
    無法對映者保留 routeId 原文並標示,不臆造。
  5. ⚠️ 本頁不含「容錯切換」操作——僅呈現狀態;
    切換屬維運程序(gateway 之 refresh/部署流程),不由作業台直接觸發(避免誤觸造成路由中斷)。

9.10.5 未竟

  • predicates 之呈現為原文透傳(如 Path=/auth-api/**)——不解析為結構化欄位(避免過度設計)。
  • 本端點不涵蓋非 HTTP 之連線(DB/Kafka/Redis)——那屬各服務之 /actuator/health 細部
    與 Prometheus 指標範疇(§9.8.5 同一原則)。
  • gateway 端點回傳之 filters(過濾器原文)未由 bff-web 呈現——§9.10.3 之 BFF 契約
    未含該欄位(作業台僅需「入口在哪」)。若日後需於頁面顯示改寫規則,須同步擴充
    §9.10.3 契約與 SDD-007 §5.4.27
    ,不逕行加欄位。
  • gateway 端點無自訂錯誤碼:SDD-006 §3.4 之 CC-GW-* 僅 5 碼(1001/1002/1003/4290/5001),
    無「路由查詢失敗」之語意,且 CSP-003 禁止 Coder 自創碼——故不設錯誤映射,
    失敗時由 bff-web 以 routesAvailable=false 降級呈現(§9.10.4 #1)。

9.11 營運總覽聚合端點(bff-web)與作業台總覽頁(2026-10-01)

緣由:SDD-007 §5.1.2.3 之桶 C「營運總覽」(/dashboard)——原記
「跨服務聚合——須先於 bff-web 立聚合端點(§5.4.24 之健康聚合為其子集)」。
依「規格先行」:先立本節 → 改 tools/gen_openapi.py → 重生契約 → 實作 →
SDD-007 §5.4.28 逐鈕定義 → 頁面。
BEAM PMO 2026-10-01 裁定採「挑投報率最高者,先補後端、再規格先行、再實作」——本頁為選定目標。

9.11.1 ★ 範圍界定(務必先讀)

面向 本版 說明
系統營運面 ✅ 本版 服務健康摘要/批次作業摘要/端點連線摘要——bff-web 自足,不需新增領域服務端點
業務 KPI 面 ⚪ 第二階段 發卡量/授權量/待辦件數等,需各領域服務新增統計端點

⚠️ 回訪查證(2026-10-01):掃 contracts/openapi/*.yaml 全數 17 服務,
皆無「統計/摘要」端點(僅 CRUD 與列表)。故業務 KPI 無法由既有端點推得,
不得以「服務有端點」推斷「已能呈現業務指標」。
→ 本版明載為系統營運面;業務 KPI 另立 CR(見 §9.11.5)。

9.11.2 端點規格

GET /api/v1/bff/dashboard(回應 BffDashboard,200)

欄位 型別 說明
checkedAt date-time 本次彙整時刻
services object total/up/down/overall(UP/DEGRADED/DOWN,判定同 §9.8.2)
batch object 依統一狀態分組(§9.9.2)計數:pending/running/succeeded/warning/failed/unknown/unavailable。⚠️ unknown 必須計入——服務回傳未定義狀態時如實歸此,不得略過(略過會使計數總和對不上,且掩蓋異常狀態)
endpoints object routes(gateway 路由數)/connectionsUp/connectionsDown

各區塊皆可為 null(該區塊聚合失敗時)——前端須如實顯示「不可用」,不得以 0 充數。

9.11.3 行為要求

  1. 複用既有聚合服務(HealthAggregationService/BatchMonitorService/
    EndpointManagementService,同一 JVM)——不重複實作探測邏輯;
    三者並行執行,避免回應時間線性累加(三區塊各自之逾時語意沿用原服務)。
  2. 區塊降級:任一區塊失敗 → 該區塊為 null,不拖垮整體——
    與 BffSection 之「單一下游失敗不拖垮整體」同一政策(SAD-004 §2.2)。
  3. 不快取——與 §9.8.3 #3 同(快取會使畫面顯示過期狀態)。
  4. fail-closed:無目標 → services.overall 回 DOWN(§9.8.6),不以樂觀預設值掩蓋。

9.11.4 與 §9.8~§9.10 之關係(非重複造頁)

本端點為摘要層(一眼看全貌);/system/health//system/batch//system/endpoints 為明細層(逐項檢視)。
→ /dashboard 不取代該三頁,亦不重複其探測邏輯(複用,見 §9.11.3 #1)。

9.11.5 未竟(誠實揭露)

  • 業務 KPI 未納入(見 §9.11.1)——需各領域服務提供統計端點,建議另立 CR。
  • 本端點不聚合歷史趨勢與告警規則——仍由 Prometheus/Grafana 負責(同 §9.8.4 之定位說明)。

9.11.6 業務 KPI 區塊(BffDashboard.business,CR-026)

CR-026 補實作「業務 KPI 第二階段」——BffDashboard 新增 business 欄位(oneOf BffDashboardBusiness 或 null)。

聚合來源:各領域服務之統計端點(card-service cards/stats+card-applications/stats、authorization-service authorizations/stats、dispute-service disputes/stats),由 BusinessStatsService(application)並行 4 路 CompletableFuture 呼叫(沿用 token-relay 之 DelegatingSecurityContextExecutor 跨執行緒傳遞 SecurityContext),逾時 STATS_TIMEOUT_MS=5000。

business 之 4 子塊(各 oneOf schema-or-null,獨立降級):

子塊 來源端點 欄位
cards GET /cards/stats total/byStatus/byBlockCode
applications GET /card-applications/stats total/byStatus/pendingReview
authorizations GET /authorizations/stats total/today/byDecision/pendingUpload
disputes GET /disputes/stats total/byStatus/openCases

⚠️ 獨立降級:任一統計端點取不到(逾時/非 200/解析失敗)→ 該子塊為 null,不以 0 充數;其餘子塊不受影響。DashboardAggregationService 之 business 區塊失敗 → 整體 business=null(區塊級降級),與 §9.11.3 #2 同政策。


9.12 分流比例管理端點(api-gateway)與作業台分流頁(2026-10-01)

緣由:SDD-007 §5.1.2.3 桶 C 之「分流比例管理(/system/split-ratio)」原記
「僅 actuator」——即機制已實作(SAD-004 §2.4:CycleSplitDecision +
CycleSplitGlobalFilter + CycleSplitProperties,POC-13 已驗收),
惟只有 POST /actuator/refresh,無管理端點:維運無法從作業台讀取當前比例,
亦無法在畫面調整/回退——必須登入機器改 config/cycle-split.yml。
本節為該缺口之規格先行補建(比照 #A-101/#A-102/#A-103 模式)。

9.12.1 選頁理由(投報率)

比較項 說明
機制是否已存在 ✅ 完整存在(SAD-004 §2.4;POC-13 七項驗收通過)——缺的只是管理面
是否需新領域模型 ❌ 不需要(組態即資料;CycleSplitProperties 已為 @ConfigurationProperties)
是否會造空殼/假資料 ❌ 不會——讀的是真實生效值,寫的是真實熱更新檔
業務價值 ★ 灰度放量與回退為遷移期最高頻之維運操作(BRD-001 §10.3 C-03 波次 W0→W4);回退速度直接對應事故處置時間

9.12.2 端點規格(api-gateway 自供;同 §9.10.2 之既有慣例)

方法 路徑 說明
GET /api/v1/gateway/split-ratio 讀取當前生效之分流組態
PUT /api/v1/gateway/split-ratio 調整 enabled/percentage 並觸發熱更新

SplitRatioView(回應):

欄位 型別 說明
enabled boolean 是否啟用分流干預
percentage integer 分流比例 0–100(新系統承接比例)
legacyUri string 舊系統(Legacy ECS)轉接目標
merchantHeader string 承載商店代號之標頭(分流維度)
pathPrefix string 分流作用之路徑前綴
configFile string | null 熱更新來源檔之實際路徑(解析 spring.config.import 之 file: 條目);未設定時為 null
configFileWritable boolean 該檔是否存在且可寫(false 時 PUT 必失敗——先揭露,不讓維運白試)
effectiveMatchesRequest boolean | null 僅 PUT 回應:套用後重讀之值是否與請求一致(誠實驗證);GET 為 null
warning string | null 僅 PUT 回應:effectiveMatchesRequest=false 時之說明;其餘為 null

SplitRatioUpdateRequest(請求):enabled(boolean,選填)、percentage(integer,必填、0..100)。

9.12.3 行為要求

  1. 驗證:percentage 必填且須為整數 0–100 → 不符回 400 CC-PLT-4003(不自行另立錯誤碼,CSP-003 §2)。⚠️ 由控制器自行驗證並組裝錯誤封套——本模組無 common-spring 之全域例外處理器(該類別非 auto-configuration,且本模組未掃描其套件),故不使用 Bean Validation 之自動例外轉譯。
  2. 定點行替換寫檔:僅改寫 cycle-split.yml 之 enabled:/percentage: 兩行——保留檔內註解與其餘鍵;不重寫整檔(避免抹掉維運註記)。
  3. 觸發熱更新:呼叫 ContextRefresher.refresh()(等同 POST /actuator/refresh,SAD-004 §2.4 之既有機制)——不重啟。
  4. 回讀驗證(★ 誠實原則):由重綁後之 CycleSplitProperties 讀回實際生效值;與請求不符時 effectiveMatchesRequest=false + warning(不以請求值冒充生效值)。
  5. 稽核:記 INFO 日誌(traceId、before → after、目標檔)——對應 SAD-004 §2.4 之「每一筆轉接均須有可追溯之 LOG」之同源要求。
  6. 失敗不靜默:檔不可寫/refresh 拋錯 → 500 CC-PLT-5000 並帶原因(不得回 200 假成功)。
  7. 不快取。
  8. 安全性:端點不另設白名單——沿用閘道之既有認證鏈(GatewaySecurityConfig:啟用時除健康探測外一律須驗 JWT)。⚠️ 角色級授權未實作(見 §9.12.5)。

9.12.4 前端(/system/split-ratio)

依 SDD-007 §5.4.30 之逐鈕定義實作;開發期由 Vite 代理 /gateway-api → api-gateway:8100。

9.12.5 未竟(誠實揭露)

  • 角色級授權未實作:任何通過閘道認證之使用者皆可調整比例——正式環境應限維運角色(待 SAD-005 §4 之角色矩陣落地)。
  • 未寫入稽核表:僅記日誌(無 audit_trail 落庫)——閘道無資料庫(SAD-001 AP-02)。
  • 不支援回退歷史:不保存比例變更歷程(需查日誌)。
  • 僅單一組態檔:多實例部署時各實例之 cycle-split.yml 須自行同步(本端點只改本實例之檔)——⚠️ 正式環境應改為集中組態來源(如 Config Server)。
  • 請求主體非合法 JSON 時之回應非本封套:該情境由 WebFlux 於進入控制器前擋下,回應為框架預設格式(非 ApiResponse)——此為已知落差(CSP-003 §3 要求所有回應一律套用封套;本模組無全域例外處理器,故無法覆蓋此路徑)。
  • GET 讀的是生效值、非檔原文:若組態檔與生效值不一致(例如改檔後未 refresh),GET 呈現的是生效值——此為刻意(維運要問的是「現在怎麼分流」),惟畫面不呈現檔內原文。

9.5 已知限制(本次未解決,誠實揭露)

  1. service_name 標籤恆為 unknown_service——實測三種寫法
    (move to: resource[...]、add field: resource[...] value: EXPR(...)、OTTL 括號鏈式)
    皆無法將 service.name 寫入 resource,且不報錯。
    替代:Loki 自動萃取 body 之 service.name 為 service_name_extracted,仍可依服務辨識。
    根治方案:改為「每服務一個 filelog receiver + 靜態 resource」,或改採 Grafana Alloy。
  2. ECS 欄位名與 CSP-004 §3 不一致 → ✅ 已解除(BEAM PMO 裁定「改規格」,CSP-004 升 1.1)。
    原落差:規格要求 ts/level/msg/correlationId/event,
    實測輸出為 @timestamp/log.level/message,且無 event。

    處置(規格遷就實測,非放寬要求):CSP-004 §3.1 欄位對照改為實測值;
    §3.2 明載 event 事件碼本期不實作(專案尚未定義事件碼表,且 CSP-003 §2 禁止 Coder 自創碼,
    故不設恆為空值之欄位);§6 廢除第二套 correlationId,統一以 OTel traceId 為準。

    ★ 2026-09-21 複核實測(真實 posting-service.json,137 筆):
    @timestamp 137/log(含 level)137/service(含 name)137/message 137/
    traceId 4/spanId 4/ecs.version 137——與 CSP-004 §3.1 完全一致,且無 event 欄位(符合 §3.2)。
    traceId 僅 4 筆係取樣率 TRACING_SAMPLING 預設 0.1 所致,非缺漏。

    樣本:{"@timestamp":"…","log":{"level":"DEBUG",…},"service":{"name":"posting-service",…}, "message":"…","traceId":"ce98fa43…","spanId":"ed89ddf5…","ecs":{"version":"8.11"}}
  3. ✅ API 回應之 traceId 與日誌之 traceId 為不同值 → 已修(2026-09-20,C-29 程式面)。
    實測同一請求:回應 10b8a469…、日誌 894ff808…,兩套 ID 無法相互對照。

    根因比「有兩套 ID」更嚴重(本次實測盤點才發現):common-spring 之
    CorrelationIdFilter 從未被註冊為 bean——AutoConfiguration.imports 只登錄
    Security×2 + ServiceClient,該類別亦無 @Component,故 17 個服務之 component scan
    皆不拾取。結果 MDC.get("trace_id") 恆為 null,TraceContext.currentTraceId()
    每次呼叫都現產一個新的亂數——回應永遠帶著一個「看似合法」之 32 碼,
    實則與任何日誌都對不起來。屬**「靜默不做」型缺陷**(不報錯、不為空、功能卻不存在)。

    處置:依 CSP-004 §6(BEAM PMO 2026-09-20 裁定「以 OTel trace ID 為準」),
    TraceContext 改為優先取 OTel 當前 span 之 traceId,其次 MDC,最後才產生;
    OTel 缺席時降級並只記一次 WARN(使「刻意不接」與「忘了接」可區分)。
    新增 TraceContextTest 7 例,並以負樣本驗證判定力(停用 OTel 優先 → 3 例精準轉紅)。
    common-spring 53 → 60 測試、0 失敗。

    ✅ 已裁定並實作(BEAM PMO 2026-09-21 18:57:保留並註冊):CorrelationIdFilter 由
    CorrelationIdAutoConfiguration(已登錄 AutoConfiguration.imports)以 FilterRegistrationBean
    註冊,並顯式指定 ORDER(LOWEST_PRECEDENCE - 100)以確保晚於 OTel 觀測範圍——
    故 MDC 與 OTel 不會出現兩個值(resolve() 之優先序:OTel span → traceparent → X-Correlation-Id → 產生)。
    測試:CorrelationIdFilterTest/TraceContextTest。啟動日誌可見 Filter 'correlationIdFilter' configured for use。

    ⚠️ 原「待裁定/未擅自註冊」之記載係過期(早於 09-21 之裁定),已更正。
  4. 僅 party-service 完成接線(觀測 1/17) → 已於 §9.6 推廣至 19/19 並逐模組實測
  5. 結構化日誌無輪轉設定 → ✅ 已查明為誤載(2026-09-21 實測):
    原記載「logging.file.name 未配置 max-size/max-history,長時間執行會持續增長」——
    實測顯示輪轉正常發生:ops/local/logs/ 內確有
    posting-service.json.2026-09-20.0.gz/statement-service.json.…/api-gateway.json.… 等
    5 個輪轉檔,logs 目錄總量僅 1.4M。

    原因:Spring Boot 於啟用 logging.file.name 時即帶有預設輪轉組態
    (max-size 10MB、max-history 7、檔名樣式 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz),
    毋須於 application.yml 顯式宣告即生效——「組態檔沒寫」≠「沒啟用」。

    ⚠️ 仍存之真實顧慮(非「無輪轉」):total-size-cap 預設為 0(無上限);
    19 個服務之最壞情況約為 7 × 10MB × 19 ≈ 1.3GB。

    ✅ 已裁定並實作(BEAM PMO 2026-10-01:5GB;GOV-007 §3):19 個 application.yml 均設
    logging.logback.rollingpolicy.total-size-cap: 5GB(並同步產生器模板)。
    ⚠️ 併更正本節原建議之屬性名:原寫 logging.file.total-size-cap,該屬性不存在;
    Spring Boot 4.1.1 之正確屬性為 logging.logback.rollingpolicy.total-size-cap
    (預設 0B=無上限;實查 spring-configuration-metadata.json 確認)。
    ⚠️ per-service 語意(誠實揭露):Logback 之 total-size-cap 係 per-appender;
    本地 19 模組共用 ops/local/logs/ 時,理論上限為「19 × 5GB」——惟 max-history: 7 × max-file-size: 10MB
    先約束單服務約 70MB,故本地實測總量(~1.3GB)遠低於 5GB;設 5GB 為硬天花板
    (供日後放寬 max-history 時仍受約束)。若需「本地目錄總量 ≤ 5GB」之硬保證,應改設每服務 ≈ 256MB。
  6. Grafana 儀表板尚未建立(§8 已列為待 SRE 凍結)。
(55-55/67)