# CR-017　`C-6`～`C-9` 四服務缺口總盤點與裁定建議（dispute／report／migration／notification）

| 項目 | 內容 |
|---|---|
| 文件編號 | CR-017 |
| 版本 | **1.5（`C-6` ✅ §9.1；`C-7` ✅ §9.2；`C-8` ✅ §9.3；**`C-9` ✅ §9.4——通道抽象先行**；**四服務全數結案**。`SCS-016` → 1.1、`SDD-001` → 1.13、`SDD-005` → 1.7、`SDD-006` → 122 碼）** |
| 提出日 | 2026-09-22 |
| 提出者 | WorkBuddy AI（開發團隊） |
| 緣起 | BEAM PMO 指示：「**先一次盤點 C-6～C-9 的缺口再逐支實作**」 |
| 盤點方法 | ①**Oracle 實測**（`all_tables`／`all_constraints`，2026-09-22）②**契約解析**（`contracts/openapi/*.yaml` 逐 schema 抽欄位與列舉）③**SCS 規格比對**（`SCS-012`／`015`／`016`／`017`） |
| 關聯前例 | `CR-015`（merchant）、`CR-016`（clearing）——同為「動工前查證 → 立 CR 取裁定 → 實作」 |

---

## 1. 盤點總表（一眼看盡）

| 服務 | 埠 | 契約端點 | DDL 現況（實測） | 缺口嚴重度 |
|---|---|---|---|---|
| **C-6** `dispute` | 8093 | 3（開案／查詢／**chargeback**） | `CC_DIS`：**2 表已備**（`DISPUTE_CASES`／`DISPUTE_TIMELINES`） | 🟡 **值域嚴重脫鉤**（兩處正交維度混用）＋缺 3 欄 |
| **C-7** `report` | 8098 | 2（送 job／查 job） | `CC_RPT`：**0 表**（V1 為空） | 🔴 **需新建表**（2 張）＋輸出媒體未定 |
| **C-8** `migration` | 8096 | 2（送 job／查 legacy） | `CC_MIG`：**0 表** | 🔴 **需新建表**（3 張）＋**契約與 SCS 粒度不一致**＋外部資料源 |
| **C-9** `notification` | 8097 | 2（送通知／查通知） | `CC_NOTIF`：**0 表** | 🔴 **需新建表**（2 張）＋**發送通道為真阻塞**（通道抽象先行） |

> 四服務**皆僅有骨架**（`*ServiceApplication` ＋ `LayeringTest`），與 `C-4`／`C-5` 起點相同。

---

## 2. C-6 `dispute-service`（`CC_DIS` 已有 2 表）

### 2.1 ⛔ 兩處「正交維度混用」（核心發現）

**① `disputeType`——「爭議原因」與「程序類型」混用**

| 來源 | 值域 | 語意 |
|---|---|---|
| 契約 `OpenDisputeRequest.disputeType` | `UNAUTHORIZED`／`DUPLICATE`／`GOODS_SERVICES`／`OTHER`（4） | **爭議原因**（為何爭議） |
| DDL `DISPUTE_CASES.dispute_type`（實測） | `CHARGEBACK`／`INQUIRY`／`RETROACTIVE`／`FRAUD`／`DUPLICATE`（5） | **程序類型**（CHARGEBACK／INQUIRY／RETROACTIVE）**與原因**（FRAUD／DUPLICATE）**混用** |

交集僅 `DUPLICATE`——**與 `CR-008`／`CR-012`／`CR-015` 同一類問題**（兩軸塞進一欄）。

**② `stage`——「卡組織程序階段」與「內部作業階段」混用**

| 來源 | 值域 | 語意 |
|---|---|---|
| 契約 `DisputeView.stage` | `OPENED`／`EVIDENCE_COLLECTING`／`UNDER_REVIEW`／`ARBITRATION`／`RESOLVED_ISSUER`／`RESOLVED_ACQUIRER`／`CLOSED`（7） | **內部作業階段**（本行怎麼處理） |
| DDL `DISPUTE_CASES.stage`（實測） | `INQUIRY`／`FIRST_PRESENTMENT`／`SECOND_PRESENTMENT`／`PRE_ARB`／`ARBITRATION`／`CLOSED`（6） | **卡組織程序階段**（`SDD-005 §7.1` 已明載） |

交集僅 `ARBITRATION`／`CLOSED`——**亦為兩軸**（`SDD-005 §7.1` 之階段 vs 本行作業流程）。

### 2.2 其餘缺口

| # | 契約元素 | DDL 現況 | 判定 |
|---|---|---|---|
| a | `ChargebackRequest.chargebackType`（`FIRST`／`SECOND`／`ARBITRATION`） | **無對應欄**（DDL 之 `STAGE` 有 `FIRST_PRESENTMENT` 等，惟語意為組織階段、非本行提交類型） | 需**新增欄** |
| b | `ChargebackRequest.reason` | 無對應欄（`EVIDENCE_SUBMITTED` 為 JSON，語意不同） | 需**新增欄** |
| c | `ChargebackRequest.evidence` | `EVIDENCE_SUBMITTED` JSON 已有 | 可沿用（或新增 `CHARGEBACK_EVIDENCE`） |
| d | `DisputeView.orgDeadline`／`handler` | `RESPONSE_DEADLINE`／`ASSIGNED_TO` 已有 | **僅命名對映**，無缺口 |
| e | `disputeNo`／`amount`／`openedAt`／`closedAt`／`disputeId` | 皆有 | ✅ |
| f | `liability`（DDL 4 值） | 契約未回傳 | 正交屬性（`SDD-005 §7.3`）——**契約未回傳可接受**，建議日後補 |
| g | `DISPUTE_TIMELINES`（稽核軌跡） | 契約未提 | ✅ 內部用，不回傳 |

---

## 3. C-7 `report-service`（`CC_RPT` **0 表**）

**SCS-017 §5 已自載「DDL 待補」與設計意圖**（非無中生有）：
- `REPORT_DEFINITIONS`（`REPORT_CODE` UNIQUE、定義 JSON、排程 Cron、格式）
- `REPORT_JOBS`（`REPORT_CODE`、狀態、參數、輸出參照、完成時間）

| # | 契約元素 | 需落之欄 | 備註 |
|---|---|---|---|
| a | `ReportJobRequest.reportCode`*／`parameters`*（物件）／`format`（`CSV`／`XLSX`／`PDF`／`JSON`） | `REPORT_JOBS.REPORT_CODE`／`PARAMETERS` JSON／`FORMAT` CHECK | format 值域由契約定（DDL 新增即對齊） |
| b | `ReportJobView.status`（`QUEUED`／`RUNNING`／`COMPLETED`／`FAILED`） | `STATUS` CHECK 4 值 | 同契約 |
| c | `rowCount`／`downloadUrl`／`startedAt`／`finishedAt` | `ROW_COUNT`／`DOWNLOAD_URL`／起訖時間 | `DOWNLOAD_URL` 指向**物件儲存**（本地已有 MinIO，見鐵則） |
| d | **報表產生器**（各報表之查詢邏輯） | — | ⚠️ **範圍須裁定**：本期僅 **job 生命週期**（送件／查詢）？或含實作 1～2 支示範報表？ |
| e | 報表主體之資料來源 | — | SCS-017 註「報表主體建議置於 `rpt` Postgres 讀模型（CQRS 讀側）」→ 本期是否觸及 Postgres？ |

---

## 4. C-8 `migration-service`（`CC_MIG` **0 表**）

**SCS-015 §5 已自載設計意圖**：`MIGRATION_WAVES`（波次）／`MIGRATION_CHECKPOINTS`／`LEGACY_ACCOUNT_MAP`。

### 4.1 ⛔ 契約與 SCS 之**粒度不一致**

| 來源 | 概念 | 值域 |
|---|---|---|
| 契約 `MigrationJobRequest` | **Job**：`migrationType`（`ACCOUNT`／`CARD`／`BALANCE`／`POINTS`／`MERCHANT`）＋`sourceRef`＋`dryRun` | — |
| 契約 `MigrationJobView` | `status`（`RUNNING`／`COMPLETED`／`FAILED`）、`processedCount`／`successCount`／`errorCount`／`errorDetail` | — |
| SCS-015 §5 | **Wave（波次 W0–W4）**：`WAVE_CODE`、波次狀態、起訖、影響範圍 | `SDD-005` 待正式化（`GOV-006` 2.3.6 註） |

→ 「Job（一次遷移執行）」與「Wave（波次，可含多個 Job）」為**不同粒度**——須裁定：
**(a)** 以契約之 `Job` 為主，Wave 由 `WAVE_CODE` 標籤歸類（單表）；
**(b)** 建 `MIGRATION_WAVES` ＋ `MIGRATION_JOBS` 兩層（符合 SCS 意圖，契約加 `waveCode`）。

### 4.2 其餘缺口

| # | 項目 | 說明 |
|---|---|---|
| a | `LegacyAccountView`（`legacyId`／`accountNo`／`migrated`／`migratedAt`／`snapshot`） | 對映至 `LEGACY_ACCOUNT_MAP` |
| b | **舊系統資料源** | `sourceRef`（如 `LEGACY_ACCT_2026Q1`）指向 **Legacy ECS**（外部）→ 本期可實作 **`dryRun` 與對映查詢**，**實際搬遷需資料源**（列限制） |
| c | 波次狀態機 | `GOV-006` 2.3.6 已註「待 `SDD-005` 正式化」→ 本期不新增狀態機（避免虛構） |

---

## 5. C-9 `notification-service`（`CC_NOTIF` **0 表**）

**SCS-016 §5 已自載設計意圖**：`NOTIFICATION_TEMPLATES`／`NOTIFICATIONS`。

| # | 契約元素 | 需落之欄 |
|---|---|---|
| a | `NotificationRequest.accountId`*／`partyId`／`templateCode`*／`variables`*（物件）／`preferredChannels`（陣列）／`amount` | `NOTIFICATIONS`：`TEMPLATE_CODE`／`VARIABLES` JSON／`PREFERRED_CHANNELS` JSON／`AMOUNT` |
| b | `NotificationView.status`（`QUEUED`／`SENT`／`FAILED`）／`channels`／`sentAt`／`failReason` | `STATUS` CHECK 3 值／`CHANNELS` JSON／`SENT_AT`／`FAIL_REASON` |
| c | **發送通道** | 🔴 **真阻塞**（鐵則：真阻塞僅 3 類，其一為「通知發送通道（僅發送）」）→ 本期**通道抽象先行**（`GOV-006` 2.3.7 明載）：定義 `NotificationChannelPort` ＋ **本地 Log 通道**（stand-in），**實際 SMS／Email／推播未接** |
| d | 模板管理 | `NOTIFICATION_TEMPLATES`（`TEMPLATE_CODE` UNIQUE、通道、語言、內文、變數定義）——**本期是否需要管理端點？** 契約無模板端點 → 建議**僅建表＋內建少量種子**，不做 CRUD |

---

## 6. 共通發現（跨四服務）

1. **三個 schema 完全無表**（`CC_RPT`／`CC_MIG`／`CC_NOTIF`），且 `SDD-001 §3` **僅涵蓋 14 個 bounded context**
   → 新建表須**同步補 `SDD-001` 之 DDL 章節**（設計權威）＋以 **V2 遷移**交付（`V1` 已套用、**凍結**，`SAD-003 §9`）。
2. **SCS-015／016／017 均已自載「DDL 待補」與設計意圖**——新表設計**有規格依據**，非無中生有；
   惟實際欄位與約束**須以 Oracle 實測值回填**（不得憑印象）。
3. **四服務皆僅骨架**，且契約端點數少（3／2／2／2＝**9 端點**）——與 `C-4`（3 端點）／`C-5`（3 端點）同規模，**逐支實作可行**。
4. **兩個「正交維度混用」**（C-6 之 `disputeType`／`stage`）——與 `CR-008`／`CR-012`／`CR-015` 同類，
   建議沿用**雙欄位**解法（不為相容而刪值）。

---

## 7. 建議裁定與實作順序

| # | 議題 | 建議 | 需裁定 |
|---|---|---|---|
| A | C-6 `disputeType` 雙軸 | **新增 `DISPUTE_REASON`**（契約 4 值：`UNAUTHORIZED`／`DUPLICATE`／`GOODS_SERVICES`／`OTHER`）；`DISPUTE_TYPE` **保留程序類型**（DDL 值域擴充為 3 值 `CHARGEBACK`／`INQUIRY`／`RETROACTIVE`＋保留 `FRAUD`／`DUPLICATE` 供相容）；契約 `disputeType` **對映 `DISPUTE_REASON`** | ✔ |
| B | C-6 `stage` 雙軸 | **新增 `WORKFLOW_STAGE`**（契約 7 值）；`STAGE` 保留**卡組織程序階段**（`SDD-005 §7.1`，契約另以 `orgStage` 回傳） | ✔ |
| C | C-6 chargeback 欄 | 新增 `CHARGEBACK_TYPE`（3 值）、`CHARGEBACK_REASON` | — |
| D | C-7 範圍 | 建 `REPORT_JOBS`（＋`REPORT_DEFINITIONS`）；**本期僅 job 生命週期**（送件／查詢／狀態流轉），**報表產生器與 Postgres 讀模型不做**（列限制） | ✔ |
| E | C-8 粒度 | **建議 (b)**：`MIGRATION_WAVES` ＋ `MIGRATION_JOBS` 兩層（符合 SCS-015 意圖，契約加 `waveCode`）——惟若求最小，可採 (a) 單表 | ✔ |
| F | C-9 通道 | **通道抽象先行**（`NotificationChannelPort` ＋ 本地 Log 通道 stand-in）；實際發送列為真阻塞 | — |
| G | 三 schema 之 DDL | 一律 **V2 遷移** ＋ **`SDD-001` 新增三節**（`rpt`／`mig`／`notif`）＋ `SCS-015/016/017` §5 回填實測值 | — |

**建議實作順序**（依 BEAM PMO 原列序，惟註明負擔）：

```
C-6 dispute      ← 表已備、缺口明確（2 處雙軸＋3 欄）      較輕
C-7 report       ← 新建 2 表＋job 生命週期                  中等
C-8 migration    ← 新建 2～3 表＋對映＋外部資料源          最重
C-9 notification ← 新建 2 表＋通道抽象                     中等
```

---

## 8. 裁定結果（BEAM PMO 2026-09-22）

| # | 議題 | 裁定 | 影響 |
|---|---|---|---|
| A | C-6 `disputeType` 雙軸 | ✅ **採雙欄位** | 新增 `DISPUTE_REASON`（契約 4 值）；`DISPUTE_TYPE` 保留程序類型 |
| B | C-6 `stage` 雙軸 | ✅ **採雙欄位** | 新增 `WORKFLOW_STAGE`（契約 7 值）；`STAGE` 保留卡組織程序階段；契約另以 `orgStage` 回傳 |
| C | C-6 chargeback 欄 | ✅ 補欄 | `CHARGEBACK_TYPE`（3 值）、`CHARGEBACK_REASON` |
| D | C-7 範圍 | ⚠️ **含 Postgres 讀模型**（**較建議更大**） | 除 `REPORT_JOBS`／`REPORT_DEFINITIONS`（Oracle）外，**另建 `rpt` Postgres 讀模型**——見 §8.1 之依賴揭露 |
| E | C-8 粒度 | ✅ **兩層：`MIGRATION_WAVES` ＋ `MIGRATION_JOBS`** | 契約加 `waveCode`；符合 `SCS-015 §5` 意圖 |
| F | C-9 通道 | ✅ 通道抽象先行 | `NotificationChannelPort` ＋ 本地 Log 通道（stand-in）；實際發送列真阻塞 |
| G | 三 schema DDL | ✅ V2 ＋ 補 `SDD-001` 三節 | `rpt`／`mig`／`notif` 各補 DDL 章節，`SCS-015/016/017 §5` 回填實測值 |
| — | 實作順序 | ✅ **依原序** `C-6 → C-7 → C-8 → C-9` | — |

### 8.1 ⚠️ 裁定 D 之依賴揭露（**須先講清楚，以免實作時才發現**）

BEAM PMO 裁定 C-7 **含 Postgres 讀模型**（CQRS 讀側），此範圍較我方建議為大，其**前置依賴**如下：

| # | 依賴 | 現況（實測／文件） | 影響與建議 |
|---|---|---|---|
| 1 | **Postgres `rpt` schema 之讀模型設計** | **尚未定義**（`SDD-001 §3` 未涵蓋；`SCS-017 §5` 僅註「報表主體建議置於 `rpt` 讀模型」） | 須先設計讀模型表（報表主體：授權彙總／交易明細／帳務快照等）→ **屬新設計，須再經裁定或於實作時提出** |
| 2 | **同步機制（Oracle → Postgres 投影）** | **未實作**；`GOV-006` **4.8 W-2 Kafka 消費端 ⬜ 未實作**（生產端已備） | **讀模型無資料來源**——三條可行路徑：**(i)** 批次投影（排程讀 Oracle 寫 Postgres，可本地落地）；**(ii)** 先建 Kafka 消費端（W-2，較重）；**(iii)** 本期僅建 schema 與投影**介面**，實作待 W-2。**建議 (i)**（不阻塞、可實測、且與 `#A-79` 之排程慣例一致） |
| 3 | 報表輸出媒體（`downloadUrl`） | 本地已有 **MinIO**（鐵則：物件儲存非真阻塞） | 可落地；惟 `downloadUrl` 之產生與檔案寫入須一併實作 |
| 4 | 報表定義（`REPORT_DEFINITIONS` 之內容） | 契約僅示例 `DAILY_AUTH_SUMMARY` | 本期建議**內建 1～2 支示範報表定義**（否則 job 無事可做＝空殼） |

> **我方建議之落地切法**（供實作時採用，若與 BEAM PMO 預期不同請即指正）：
> **①** Oracle `CC_RPT`：`REPORT_DEFINITIONS` ＋ `REPORT_JOBS`（作業指標，契約直接對應）；
> **②** Postgres `rpt`：**讀模型表**（報表主體）＋**批次投影**（排程自 Oracle 權威讀入，stand-in 取代 Kafka 消費端）；
> **③** 報表產生器：讀 Postgres 讀模型 → 輸出 CSV／JSON（XLSX／PDF 需額外函式庫，建議先 CSV／JSON，其餘列限制）。

---

## 9. 逐支實作結果

### 9.1 `C-6` `dispute-service`（2026-09-22 完成）

**交付**

| 項 | 內容 |
|---|---|
| 遷移 | `V2__dispute_reason_workflow_stage_chargeback.sql`——新增 `DISPUTE_REASON`（4 值）／`WORKFLOW_STAGE`（7 值）／`CHARGEBACK_TYPE`（3 值）／`CHARGEBACK_REASON`；`dispute_type` 之 CHECK 重建為具名（值域不變）；新增 `dispute_no_seq` |
| 契約 | 改 `gen_openapi.py` 後重生（**0 錯誤／74 端點**）：`disputeType` 補述「爭議原因」語意、`DisputeView` 新增 `orgStage`（6 值）、`stage` 補述「本機作業階段」 |
| 程式 | DDD 四層：`Dispute` 聚合（**7 個值域枚舉**）＋1 埠＋JDBC＋應用服務＋組裝器＋3 端點 |
| 品質 | 單測 **19 例 0 失敗**（領域 7／應用 8／既有 4）；`scan_scaffold`／`scan_faketest` 0 命中；`validate_sql` 0 違規 |

**真機 e2e（8093，六項）**

| # | 情境 | 結果 |
|---|---|---|
| ① | 開案（`disputeType=UNAUTHORIZED`） | **201**：`DSP202609000001`／`stage=OPENED`／`orgStage=INQUIRY`／時限＝開案 +30 日 |
| ② | 查詢 | **200**，狀態一致 |
| ③ | 扣款 `FIRST`（帶 evidence 物件） | **201**：`stage=UNDER_REVIEW`／`orgStage=FIRST_PRESENTMENT` |
| ④ | **重複提交 `FIRST`** | **冪等**：狀態不變、`REPRESENTMENT_COUNT` 仍為 **1**（`SM-03`） |
| ⑤ | **跳號提交 `ARBITRATION`** | **409 `CC-DSP-3001`**「目前階段 FIRST_PRESENTMENT，不得提交 ARBITRATION」 |
| ⑥ | **DB 複核** | **雙軸同列各自獨立**：`DISPUTE_REASON=UNAUTHORIZED`（原因）／`DISPUTE_TYPE=INQUIRY`（程序類型）／`STAGE=FIRST_PRESENTMENT`（組織階段）／`WORKFLOW_STAGE=UNDER_REVIEW`（作業階段）；`EVIDENCE_SUBMITTED` JSON 落地；4 個具名約束值域正確 |

**施行中追加（附理由）**

| # | 事項 | 處理 | 理由 |
|---|---|---|---|
| a | **卡組織（`org`）之來源** | 開案預設 `INTERNAL` | 契約之 `OpenDisputeRequest` **未帶 `org`**，而 DDL 之 `ORG` 為 `NOT NULL`；`INTERNAL`（本行受理、尚未向卡組織提出）與 `SDD-005 §7.1` 轉換 1 語意一致。待契約擴充後改由請求帶入 |
| b | 卡組織時限（`orgDeadline`） | 開案 +**30 日**（常數） | 契約示例為 09-17→10-17（30 日）；卡組織時限**未參數化**（無參數來源表），列為限制 |
| c | `ChargebackRequest.evidence` | 型別由 `String` 改為 **`Object`**（契約為 `object`），序列化後寫入 `EVIDENCE_SUBMITTED` | 原實作與契約不符（契約明載 `type: object`）——**契約優先** |
| d | 扣款提交之冪等 | 同一類型且已達目標階段 → 回傳現況（`SM-03`） | 更新操作無冪等鍵欄位；以狀態判斷重複執行，避免重送變 409 |

---

### 9.2 `C-7` `report-service`（2026-09-22 完成）——**含 Postgres 讀模型**

**交付**

| 項 | 內容 |
|---|---|
| Oracle 側 | `V2__report_definitions_and_jobs.sql`：`REPORT_DEFINITIONS`（含內建 `DAILY_AUTH_SUMMARY`）＋`REPORT_JOBS`（狀態 4 值／格式 4 值 CHECK）＋`report_job_no_seq` |
| **Postgres 讀模型** | `db/migration-postgres/V1__rpt_read_model.sql`：`rpt.daily_auth_summary`（日 × 通路 × 型別 × 決策，複合主鍵、**upsert 冪等**）——**CQRS 讀側首例**（實測 `rpt` schema 早已存在但全庫無服務使用） |
| 投影 | `AuthSummaryProjectionService`＋`ReportProjectionScheduler`（**追趕視窗**「N 日前 ~ 昨日」，可回補） |
| 產生 | `ReportJobService`（入隊／查詢／取件執行）＋`ReportGenerator`（CSV／JSON；輸出本地檔案）＋`ReportJobScheduler` |
| 契約 | 端點與成功碼**未變**（202／200）；故未改 `gen_openapi.py` |
| 品質 | 單測 **16 例 0 失敗**（領域 6／應用 6／既有 4）；`scan_scaffold`／`scan_faketest` 0 命中；`validate_sql` 0 違規 |

**真機 e2e（8098，五項）**

| # | 情境 | 結果 |
|---|---|---|
| ① | **投影**（Oracle `CC_AUTH` → Postgres `rpt`） | 4 列：`09-18 APPROVED 2/100000`、`09-19 APPROVED 16/351300`、`09-19 DECLINED 11/403000`、`09-19 STANDIN 1/22200`——**與 sqlplus 直查權威側逐欄一致** |
| ② | `POST /report-jobs`（CSV） | **202**／`QUEUED` |
| ③ | 排程取件後查詢 | **`COMPLETED`**／`rowCount=4`／`downloadUrl=file://…/RPTJ202609000001.csv` |
| ④ | **CSV 內容** | 表頭＋4 列，金額為**分**（`100000`／`351300`／`403000`／`22200`）——與讀模型一致 |
| ⑤ | **未支援格式 `XLSX`** | **`FAILED`**＋原因「輸出格式 XLSX 本期未支援（需額外函式庫）；已支援 CSV／JSON」，**無輸出參照**（不產空檔、不假裝成功） |
| ⑥ | 同冪等鍵重送 | 回**同一** `jobId`（未重複入隊） |

**⚠️ 施行中追加／更正（附理由）**

| # | 事項 | 處理 | 理由 |
|---|---|---|---|
| a | **物件儲存之更正** | 輸出改為**本地檔案**（`${cardcore.report.output-dir}`，`downloadUrl` 為 `file://`） | **`CR-017 §8.1` 原載「MinIO 可落地」為誤**——實測 `docker-compose.yml` **無 MinIO service**（已於 SCS-017 §5.4 更正） |
| b | **跨 schema 讀取授權**（`ops/local/init/oracle/12-dev-report-projection-grant.sql`） | `GRANT SELECT ON CC_AUTH.AUTHORIZATIONS TO CC_RPT`，**明標為本地 stand-in** | 正規同步為事件驅動（`W-2` Kafka 消費端 ⬜）；經 API 亦不可行——`authorization-service` 之 `allowed-callers` 依 `SAD-002 §5` 為**空集合**（實測）。**正式環境不得如此**（服務邊界），須改事件驅動或先修互動矩陣 |
| c | **雙資料源之兩大陷阱**（實測踩坑，已寫入程式註解） | ①兩個 `DataSource`／`JdbcTemplate` **皆明確定義**、主側標 `@Primary`；②一律以 `DataSourceProperties` 綁定（`url` 鍵）；③Postgres 之 Flyway **不註冊 bean**（由 `RptReadModelMigrator` 程式執行） | ①Boot 之 `@ConditionalOnSingleCandidate(DataSource.class)`／`@ConditionalOnMissingBean` 會**整體退讓**→ Oracle 遷移不執行、未指名之注入誤取 Postgres 模板（Oracle SQL 於 Postgres 拋 `BadSqlGrammar`）；②`DataSourceBuilder` 之鍵為 `jdbcUrl`，與慣例 `url` 不符；③任何 `Flyway` bean 會抑制 Boot 之自動配置遷移 |
| d | **Flyway 需資料庫專屬模組** | 補 `org.flywaydb:flyway-database-postgresql` | 缺之則啟動失敗「Unsupported Database: PostgreSQL 18.6」（同 Oracle 之 `flyway-database-oracle`） |
| e | 投影視窗 | 「**N 日前 ~ 昨日**」區間（非單日） | 需可**回補**（停機／延遲補正）；投影冪等故重跑無副作用 |
| f | 報表參數未提供時 | 取讀模型**全部**資料 | 本期未實作預設區間策略（如「昨日」）——以免產出**看似正確卻空無一列**之報表 |

---

### 9.3 `C-8` `migration-service`（2026-09-22 完成）——**兩層 wave＋job**

**交付**

| 項 | 內容 |
|---|---|
| 遷移 | `V2__migration_waves_jobs_and_legacy_map.sql`：**`MIGRATION_WAVES`**（波次 `W0`–`W4`，內建 5 筆種子）＋**`MIGRATION_JOBS`**（型別 5 值／狀態 3 值，皆對齊契約）＋**`LEGACY_ACCOUNT_MAPPINGS`**（舊帳對映）＋`migration_job_no_seq` |
| 契約 | `MigrationJobRequest` 加 **`waveCode`**（`W0`–`W4`，required）、`MigrationJobView` 加 `waveCode`／`dryRun`；重生 **0 錯誤／74 端點** |
| 程式 | DDD 四層：`MigrationJob`（兩層之下層）／`MigrationWave`（上層）／`LegacyAccount`；3 埠＋3 JDBC；2 應用服務＋組裝器；2 端點（POST **202**／GET **200**）＋取件排程 |
| 品質 | 單測 **17 例 0 失敗**；`scan_scaffold`／`scan_faketest` 0 命中；`validate_sql` 0 違規 |

**真機 e2e（8096，六項）**

| # | 情境 | 結果 |
|---|---|---|
| ① | 試跑 `dryRun=true`（`W0`） | **202** → 排程 → `MIGJ202609000001` **`COMPLETED` 2/2**；**`PENDING` 仍為 2**（**未變更任何列**） |
| ② | 非試跑 `dryRun=false`（`W1`） | **202** → `MIGJ202609000002` **`COMPLETED` 2/2**；`PENDING → 0`、`MIGRATED → 3` |
| ③ | 舊帳查詢 `GET /legacy-accounts/LEGACY_ACCT_0001` | **200**：`migrated=true`、`snapshot={"note":"seed","tier":"PLATINUM"}`（**JSON 解析為物件**，符契約 `type: object`） |
| ④ | **波次不存在**（`W9`） | **422 `CC-MIG-1001`**「波次不存在：W9」（兩層結構之檢核） |
| ⑤ | 同冪等鍵重送 | 回**同一** `jobId`（未重複入隊） |
| ⑥ | **DB 複核** | `MIGRATION_JOBS` 2 列（`W0 dry=1`／`W1 dry=0`，皆 `COMPLETED 2/2`）；`LEGACY_ACCOUNT_MAPPINGS` 3 列全 `MIGRATED=1`；`MIGRATION_WAVES` 5 筆種子 |

**⚠️ 施行中追加／事故（附理由，皆已揭露）**

| # | 事項 | 處理 | 理由 |
|---|---|---|---|
| a | **本期 job 語意** | 為「**對映註冊表之處理與計數**」 | 舊系統（Legacy ECS）資料源**未接入** → **不搬移任何帳務資料**；計數為**對映處理數**（`SCS-015 §5.1` 明載，**不假造搬遷**） |
| b | **SQL-04 命名違規與處置** | V2 所建之 `legacy_account_map` 違反「表名須為複數」→ **更正 V2 檔**為 `legacy_account_mappings`，並**重置本機遷移狀態**（drop 三表＋序號、刪除 V2 之 history 列）後重套 | ⚠️ **正規紀律為「已套用之遷移不可改」**（`SAD-003 §9`）——此次例外之**唯一理由**：該檔係**本輪剛建立、僅本機套用**（未離開本機、未進任何共享環境）。豁免登錄檔（`sql_exemptions.yaml`）**明訂業務主檔不適用**，故不能以豁免處理。**若該檔已進共享環境，則應改以 V3 改名遷移處理** |
| c | **`LegacyAccountView.snapshot` 型別** | 以 `Object`（解析 JSON）承載 | 契約為 `type: object`；解析失敗則**原樣回傳字串**（不靜默丟棄） |
| d | ⚠️ **過程事故（自我揭露）** | 造種子時誤以 `CREATE TABLE`（含 `EXCEPTION WHEN OTHERS THEN NULL`）預先建了 `LEGACY_ACCOUNT_MAP`，致 V2 部分套用後失敗（Oracle DDL 自動提交、無交易保護），需清理殘留＋刪除失敗之 history 列 | **教訓**：造測試資料**不得**預先建立遷移將建立之物件；`WHEN OTHERS THEN NULL` 會**吞掉真實錯誤**，屬反模式 |

---

### 9.4 `C-9` `notification-service`（2026-09-22 完成）——**通道抽象先行**

**交付**

| 項 | 內容 |
|---|---|
| 遷移 | `V2__notification_templates_and_notifications.sql`：**`NOTIFICATION_TEMPLATES`**（唯一鍵 `(TEMPLATE_CODE, CHANNEL, LANGUAGE)`＝**多通道多語**）＋**`NOTIFICATIONS`**（`STATUS` 3 值對齊契約、`RETRY_COUNT`、`FAIL_REASON`）＋`notification_no_seq`＋**內建 8 筆模板**（`AUTH_APPROVED` × 4 通道、`PAYMENT_RECEIVED` × 2、`STATEMENT_ISSUED` × 1、**`AUTH_APPROVED`/`LINE_AOA` 刻意停用**以供「無可用通道」路徑示範） |
| 程式 | DDD 四層：`Notification`（狀態守衛＋重試）／`NotificationTemplate`（渲染與變數偵測）／`NotificationMessage`／`DeliveryResult`；**3 埠**（通知／模板／**通道**）；3 JDBC；`NotificationService`；`NotificationViewAssembler`；**2 端點（POST 201／GET 200）**；`NotificationDispatchScheduler`（取件遞送） |
| **通道抽象**（裁定 F） | `NotificationChannelPort`（`supports`＋`deliver`）＋**唯一實作 `LoggingNotificationChannel`（本地 Log 通道 stand-in）**——**不呼叫任何閘道**；正式環境**僅需新增實作 bean**，領域／應用層不動 |
| 契約 | **未改契約**（2 端點、`NotificationRequest`／`NotificationView` 皆已備）；**新增 3 錯誤碼**（`SDD-006 §3.4`：`CC-NOT-1002`／`1003`／`4040`） |
| 品質 | 單測 **31 例 0 失敗**（全模組 35 例）；`scan_scaffold`／`scan_mockdata`／`scan_faketest` **各 0 命中**；`validate_sql`（**正規範圍**：`src/main/resources/db/migration`）**0 違規** |

**真機 e2e（8097，十一項）**

| # | 情境 | 結果 |
|---|---|---|
| ① | 受理（`preferredChannels=["SMS"]`） | **201**：`QUEUED`、`channels=["SMS"]` |
| ② | **同冪等鍵重送** | 回**同一** `notificationId`（`32d79c15-…`），未重複落庫 |
| ③ | 未知樣板 | **422 `CC-NOT-1001`**「通知樣板不存在或未啟用：NO_SUCH_TEMPLATE（語言 zh-TW）」 |
| ④ | 偏好與可用通道**無交集**（`LINE_AOA` 未啟用） | **422 `CC-NOT-1002`**「已啟用 `[EMAIL, LINE_BC, PUSH, SMS]`，與偏好 `[LINE_AOA]` 無交集」——**未靜默改送** |
| ⑤ | **變數不足** | **422 `CC-NOT-1003`**「通道 SMS 缺少 `[cardLast4, merchant, currency]`」——**受理期即擋**，未留下任何通知 |
| ⑥ | 非法通道值 `FAX` | **400 `CC-PLT-4003`**（參數錯誤，非 422） |
| ⑦ | 缺 `X-Channel` 標頭 | **400 `CC-PLT-4001`**（含 `details`） |
| ⑧ | 排程遞送後查詢 | **200**：**`SENT`**、`sentAt=2026-09-22T11:45:38.928587Z` |
| ⑨ | 查無通知 | **404 `CC-NOT-4040`** |
| ⑩ | 識別碼非 UUID | **400 `CC-PLT-4003`** |
| ⑪ | **超長內容**（SMS 上限 160 字元；實得 177） | `QUEUED` →（retry 1，仍 `QUEUED`）→（retry 2）→ **`FAILED`**，`failReason="SMS：通道 SMS 內容長度 177 字元超過上限 160 字元"` |
| ⑫ | **DB 複核** | `NOTIFICATIONS` 2 列：`NOTIF202609000001` `SENT`／`retry=0`／`channels=["SMS"]`／`SENT_AT 19:45:38`；`NOTIF202609000002` `FAILED`／`retry=2`／`fail_reason` 已載；`NOTIFICATION_TEMPLATES` **8 列**、`LINE_AOA` `ENABLED=0` |

**⚠️ 施行中追加／事故（附理由，皆已揭露）**

| # | 事項 | 處理 | 理由 |
|---|---|---|---|
| a | **狀態值域脫鉤之更正** | `SCS-016 §3` 原載 `{PENDING, SENT, FAILED, RETRYING}` → **更正為契約之 `{QUEUED, SENT, FAILED}`** | 契約為 API 曝露列舉之權威（前例：`C-5` 成功碼依契約更正）。`PENDING` ≠ `QUEUED`；`RETRYING` **契約未定義** |
| b | **重試不以獨立狀態表示** | 失敗時 `RETRY_COUNT + 1` 且**狀態維持 `QUEUED`**，達上限方轉 `FAILED` | 若於 DDL 增列 `RETRYING`，**契約無處回傳**、須映射回 `QUEUED`＝**無對應語意之死狀態（假綠）**。以自轉換表達，並於 `SDD-005 §13` 載明**其與 SM-03（冪等）之區別**（判準：有無狀態變更） |
| c | **`FAILED` 之可達性** | 本地 Log 通道之遞送規則含**通道內容長度上限**（`SMS` 160／`PUSH` 200／`LINE_BC`·`LINE_AOA` 1000／`EMAIL` 2000 字元） | 若 stand-in **永遠成功**，則 `FAILED` 與重試**不可達**——測試只能證明「成功會成功」，屬**假綠**。長度上限為**真實閘道之既有限制**，以其為判定使失敗路徑**可達且可測**。⚠️ 此為**本地 stand-in 之規則**，非契約或 SDD 之規定 |
| d | **收件人未解析** | `PARTY_ID` 僅為**參照**；`recipientRef` 以 `party:<uuid>` 形式記錄 | 契約 `NotificationRequest` **無收件人欄位**（實測）、`party-service` 契約 4 端點**皆非聯絡資訊**（實測）→ **不假造收件人** |
| e | **刻意不設 PII 加密欄** | `NOTIFICATIONS` **無** `AesGcmFieldEncryptor` 路徑 | 因 (d)——無來源之密文欄位＝**假綠**（`SCS-016 §5.4` ③） |
| f | **多語未啟用** | `LANGUAGE` 固定 `zh-TW` | 契約**無語言欄位**；模板仍以 `(CODE, CHANNEL, LANGUAGE)` 為鍵以備擴充 |
| g | **`SCS-016 §5.3` 之交付清單更正** | 原載「種子 `ops/local/init/oracle/13-dev-notification-seed.sql`」→ **改為「無獨立本地種子檔」** | 模板為**產品內建**、隨 V2 交付（`NOT EXISTS` 守衛、冪等）；另立本地種子將造成**同一資料兩處定義**之漂移 |
| h | **`SDD-001 §9` 統計更正** | `Bounded Context` 14 → **17**、`實體表` 47 → **55** | 前版（1.12）之數字係 **1.9 版**之值，其後 §3.16／§3.17 新增 6 表**未同步更新**；本次逐項清點更正（方法已載於該節） |

**⚠️ 發現之既有問題（非本輪引入，僅記錄、未逕改）**

| # | 事項 | 說明 |
|---|---|---|
| i | **`validate_sql` 之掃描範圍模糊** | 以**正規範圍**（`src/main/resources/db/migration`）掃描 → **0 違規**；惟若以 `backend` 全庫掃描，會命中 **C-7 之 Postgres 讀模型 5 處**（`migration-postgres/V1__rpt_read_model.sql`：`SQL-04` 表名非複數、`SQL-05` 缺 `id`／`created_at`／`updated_at`／`version`）。該檔**刻意**如此設計（`SDD-001 §3.16`：複合主鍵、無樂觀鎖），且**不在** `CSP-001 §5.3` 豁免登錄檔之適用範圍（登錄檔明訂「僅供基礎設施表」）。**建議**（需 BEAM PMO 裁定方向）：**(a)** 於 `CSP-001 §5.3` 明訂 `migration-postgres` 之處理（讀模型非 OLTP 主檔，`SQL-04`／`SQL-05` 之立法目的不適用）；或 **(b)** 納入豁免登錄檔並放寬其適用範圍（須改登錄檔之約束條款）。**本輪未逕改**——此屬 C-7 之交付物與規則適用範圍之設計決策 |

---

## 10. 誠實揭露

- 本盤點之 DDL 現況為 **Oracle 實測**（`all_tables`／`all_constraints`，2026-09-22）；
  契約欄位為**逐 schema 解析**（非抽樣）。
- **未實測**者：`CC_RPT`／`CC_MIG`／`CC_NOTIF` 之**既有資料筆數為 0**（無表，故必然為 0）——不涉及資料搬遷風險。
- C-6 之 `DISPUTE_CASES`／`DISPUTE_TIMELINES` **實測皆為 0 筆**（2026-09-22）→
  新增 `NOT NULL` 欄**無回填風險**；惟兩表之 CHECK **全為系統命名**（`SYS_C*`，實測無具名約束）
  → 擴充值域時須比照前例**動態尋得後重建為具名**（`CR-015`／`CR-016` 同一手法）。
- 本 CR **僅盤點**，不含實作；各服務之實作將**逐支**進行並各自回寫 `HANDOVER`／`GOV-006`。
- **`C-9` 之核心限制（真阻塞）**：**實際發送未接**——唯一通道實作為**本地 Log 通道 stand-in**，
  訊息**不離開本機**；「通知發送通道（僅發送）」屬鐵則所稱 3 類**真阻塞**之一
  （`GOV-006` 2.3.7）。**收件人聯絡方式亦無法解析**（契約無欄位、`party-service` 無聯絡端點）。
- **`C-9` 之本地 stand-in 規則已明標**：通道內容長度上限（`SMS` 160 字元等）為**近似值**，
  其目的在使 `FAILED`／重試路徑**可達且可測**（避免「永遠成功」之假綠）；
  正式環境須以真實閘道之限制校準（`SCS-016 §10.6`）。
- **§9.4 (i) 之掃描範圍問題已記錄但未逕改**——屬 C-7 交付物與規則適用範圍之決策，待裁定。

---

## 11. 修訂記錄

| 版本 | 日期 | 修訂內容 |
|---|---|---|
| 1.0 | 2026-09-22 | 提出。依 BEAM PMO 指示「先一次盤點再逐支實作」，完成 C-6～C-9 四服務盤點：**C-6 表已備但兩處正交維度混用＋缺 3 欄；C-7／C-8／C-9 之 schema 完全無表**（`SDD-001 §3` 僅涵蓋 14 context）；C-8 另有契約與 SCS 粒度不一致、C-9 有發送通道真阻塞。提出 7 項裁定建議與實作順序。待 BEAM PMO 裁定 |
| 1.1 | 2026-09-22 | **BEAM PMO 裁定完成**：A／B 採**雙欄位**、C 補 chargeback 欄、**D 含 Postgres 讀模型（較建議為大）**、E 採**兩層 wave＋job**、F 通道抽象先行、G 補 `SDD-001` 三節＋V2；實作順序**依原序**。新增 **§8 裁定結果**與 **§8.1 裁定 D 之依賴揭露**（Postgres 讀模型之 4 項前置依賴：schema 未定義、同步機制未實作〔`W-2` Kafka 消費端 ⬜〕、MinIO 可落地、報表定義需內建），並提出**落地切法建議**（Oracle 作業指標 ＋ Postgres 讀模型 ＋ **批次投影 stand-in** ＋ CSV／JSON 輸出） |
| 1.5 | 2026-09-22 | **`C-9` `notification-service` 實作完成並真機實測通過（見 §9.4）——`C-6`～`C-9` 四服務全數結案**。依裁定 F **通道抽象先行**（`NotificationChannelPort` ＋ 本地 Log 通道 stand-in）：V2 建 2 表＋8 筆內建模板、DDD 四層、2 端點（201／200）＋取件排程；**新增 3 錯誤碼**；單測 31 例 0 失敗；真機 e2e **十二項**（含**受理即驗證**之三種 422、**重試後 `FAILED`** 且載明原因、DB 複核）。**併揭露八項施行決策**（a 值域更正／b 重試不設獨立狀態／c `FAILED` 可達性／d 收件人未解析／e 不設 PII 欄／f 多語未啟用／g 交付清單更正／h `SDD-001 §9` 統計更正）與**一項既有問題**（i `validate_sql` 掃描範圍模糊，僅記錄未逕改）。`SCS-016` → 1.1、`SDD-001` → 1.13（新增 §3.18）、**`SDD-005` → 1.7（新增 §13 通知狀態機；原 §13／§14 遞移為 §14／§15）**、`SDD-006` → 122 碼 |
| 1.4 | 2026-09-22 | **`C-8` `migration-service` 實作完成並真機實測通過（見 §9.3）**——依裁定 E 之**兩層 `WAVE`＋`JOB`**：Oracle V2（3 表＋5 波次種子）、契約加 `waveCode`、2 端點＋取件排程；單測 17 例 0 失敗；真機 e2e 六項（**試跑不動任何列 vs 非試跑標記**、`W9` → 422、冪等、DB 複核）。**併揭露兩項事故**：①SQL-04 命名違規之**例外處置**（V2 僅本機套用故直接更正；豁免登錄檔明訂業務主檔不適用）；②造種子時**誤預建遷移將建之物件**致 V2 部分失敗（`WHEN OTHERS THEN NULL` 吞錯之反模式） |
| 1.3 | 2026-09-22 | **`C-7` `report-service` 實作完成並真機實測通過（見 §9.2）**——Oracle 作業指標（2 表＋內建定義）＋**Postgres 讀模型**（`rpt.daily_auth_summary`，**CQRS 讀側首例**）＋批次投影（追趕視窗）＋非同步作業產生（CSV／JSON）。單測 16 例 0 失敗；真機 e2e 六項（投影 4 列與權威側逐欄一致、202→COMPLETED、CSV 內容、XLSX 明確 FAILED、冪等）。**併更正 §8.1 之「MinIO 可落地」為誤**（實測無 MinIO），並揭露**跨 schema 授權 stand-in** 與**雙資料源三大陷阱**（皆已寫入程式註解） |
| 1.2 | 2026-09-22 | **`C-6` `dispute-service` 實作完成並真機實測通過**（見 §9.1）：V2 遷移（4 新欄＋具名約束＋序號）、契約重生（`orgStage` 新增／`disputeType` 語意補述）、DDD 四層＋3 端點；單測 19 例 0 失敗；真機 e2e 六項（含**雙軸同列獨立**之 DB 複核、跳號 409、重複提交冪等）。併**施行中追加四項**（`org` 預設 `INTERNAL`／時限 30 日未參數化／`evidence` 型別改 `Object` 對齊契約／扣款冪等以狀態判斷）。⚠️ **本版併修正章節編號**（插入 §9 後原 §9／§10 順移為 §10／§11，使實體順序連號）。`SCS-012` → 1.1、`SDD-001` → 1.10 |
