# 06 整合與交易時序

主要讀者：後端工程師、系統分析師、QA 與維運人員。

## 1 維護、啟動與回報

```mermaid
sequenceDiagram
    autonumber
    actor U as 同仁
    participant C as Crystal
    participant N as Northstar
    participant S as Sentry library
    participant T as 待測系統與測試資料庫
    U->>C: POST /api/v1/QAC004/query
    C-->>U: 案例與完整 caseSteps
    U->>C: PUT /api/v1/QAC004/edit
    alt 案例及步驟檢核通過
        C-->>U: status=ok
    else 欄位或業務檢核失敗
        C-->>U: HTTP 400；status=fail、errors
    end
    U->>C: POST /api/v1/QAC006/invoke
    C->>C: 檢查 NEW/RUNNING Run、Trigger 與 source
    alt 存在未完成 Run
        C-->>U: HTTP 400；E998，拒絕本次啟動
    else prod/test 且設定可用
        C->>N: GET /unauthApi/trigger/{triggerNum}?empNum=...
        N->>S: 非同步啟動；同程序呼叫
        N-->>C: result=ok，沒有 runNum
        C-->>U: status=ok，result.result=ok 或 fail
        S->>C: GET /unauthApi/crystalForNorthstar/getTestTriggerByPk/{triggerNum}
        C-->>S: Trigger、Plan、Environment 與選取案例
        S->>C: POST /unauthApi/crystalForNorthstar/insertRun
        C-->>S: Run，含 runNum
        S->>T: 依 dataSources 設定準備資料，再執行步驟
        S->>C: POST insertRunSuite／insertQaC010／insertRunStep
        S->>C: PUT updateRunStep／updateQaC010
        alt 執行流程走完
            S->>C: PUT /unauthApi/crystalForNorthstar/updateOneRunRecord
            Note over S,C: runStatus=3 FINISHED；案例仍可能 FAILED
        else 流程例外
            S->>C: PUT /unauthApi/crystalForNorthstar/updateOneRunRecord
            Note over S,C: 嘗試記錄 runStatus=4 INTERRUPTED
        end
    else Crystal 不在 prod/test 環境
        C-->>U: result.result=ok，但不呼叫 Northstar
    else 啟動請求或回應逾時
        C-->>U: 例外或逾時；結果待確認
        Note over U,N: 不自動重送啟動；背景工作可能已開始
    end
    U->>C: POST /api/v1/QAC007/init
    U->>C: POST /api/v1/QAC007/query
    U->>C: POST /api/v1/QAC007/query/case
    C-->>U: 執行、案例與步驟結果
```

Sentry 是 Northstar 載入的執行 library，不是另一個 HTTP 服務。圖中縮寫 `insertRunSuite` 等結果操作皆位於 Crystal `/unauthApi/crystalForNorthstar` 下；完整路徑與欄位見 [OpenAPI](openapi.yaml)。

### 1-1 三個專案的責任

| 元件 | 負責內容 | 整合邊界 |
| :--- | :--- | :--- |
| Crystal | 維護專案、環境、群組、案例／步驟、計畫、觸發器；儲存並查詢執行結果 | 管理 API 及執行服務使用的結果回寫 API |
| Northstar | 排程掃描與人工啟動入口，承載背景執行 | 人工啟動回應只表示入口已返回，不保證 Run 已建立 |
| Sentry | 組合執行上下文、執行 hooks 與六種步驟、準備資料、回寫結果與報告處理 | 同程序 library；透過 Crystal API 讀寫定義及結果 |
| 待測系統／資料庫 | 實際處理 HTTP、SQL 與方法呼叫 | 步驟依案例設定執行；資料不因新增案例就自動準備 |
| EKS 與外部排程 | 早上啟動供同仁使用，半夜啟動跑全量，到固定截止時間強制關閉；早上再啟動查問題 | 這是已確認需求；本次三個專案未提供可直接宣稱已完成的 EKS 開關 API |

白天與半夜共用同一套環境和資料。執行器會依 `dataSources` 設定進行資料初始化，包含中止連線與清空／重建指定測試資料，因此夜間啟動前須先結束同仁操作。案例資料、環境就緒及結束後關閉的完整交接見 [業務範疇](01_BUSINESS_FLOW_AND_SCOPE.md) 與 [BPMN 圖集](02_BPMN_PROCESS_MODELS.md)。

### 1-2 API 分組與正確用法

| 操作 | 既有 API | 呼叫端需注意 |
| :--- | :--- | :--- |
| 專案與環境 | `/api/v1/QAC001`、`/api/v1/QAC002` | 專案與環境設定資料；環境紀錄不是 EKS 開關 |
| 群組 | `/api/v1/QAC003` | 刪群組需依檢核及內容搬移規則，不是無條件刪除所有案例 |
| 載入／新增／更新案例 | POST `/api/v1/QAC004/query`；POST／PUT `/api/v1/QAC004/edit` | 更新送完整 `caseSteps`；不是單步 CRUD |
| 刪除／還原案例 | DELETE `/api/v1/QAC004/delete`；PUT `/api/v1/QAC004/restore` | 一般案例移回收區；hook 刪除不同。還原不會自動恢復 Plan 選案 |
| 計畫選案 | POST／PUT `/api/v1/QAC005/edit` | 使用完整 `selectedCaseNums`；新增案例不代表自動列入夜間全量 |
| 觸發器設定／人工執行 | `/api/v1/QAC006`；POST `/api/v1/QAC006/invoke` | Cron 是測試觸發設定，不能據此推定 EKS 排程已串接 |
| Run 清單／明細 | POST `/api/v1/QAC007/init`、`/query`、`/query/case` | 分別以專案、`runNum`、`runCaseNum` 查詢；不能把最新一筆直接當成本次啟動 |
| Northstar 啟動 | GET `/unauthApi/trigger/{triggerNum}` | `empNum` 必填；`test=true` 仍執行與寫入，不是 dry-run |
| 結果寫回 | Crystal `/unauthApi/crystalForNorthstar` | 建立 Run／Suite／Case／Step 執行紀錄及狀態；不是前端可隨意指定結果的流程 |

OpenAPI 分別設定 Crystal 與 Northstar 的 base URL，部署時填入各自 host 與 context path。管理介面實際使用 PUT／DELETE，不能依通用範本改成不存在的 POST 路徑。沒有核實到停止 Run、單步試跑、案例發布或 EKS 開關端點，故不新增這些 API。

### 1-3 啟動回應與測試結果不同

| 回應或狀態 | 正確解讀 |
| :--- | :--- |
| Crystal `status=ok` | handler 正常返回；不代表測試通過 |
| 人工觸發 `result.result=ok` | 對 Northstar 的呼叫被視為成功，或目前環境未實際呼叫 |
| Northstar `{result:ok}` | 非同步入口已返回；沒有 `runNum`／`jobId` |
| Run `FINISHED=3` | 流程走完，必須再看各 Case／Step 結果 |
| Case `PASSED=3` | 該案例步驟判定通過；與 Run 的代碼 3 語意不同 |
| 報告 API 返回成功 | 不保證郵件已送達，也不保證比較附件產生成功 |

## 2 排程、案例儲存與執行規則

### 2-1 排程路徑

Northstar 透過 GET `/unauthApi/crystalForNorthstar/getTodoTestList` 取得啟用且已到 `nextRunTime` 的觸發器，再比對來源設定。排程流程使用觸發器鍵檢查 cache，更新下次時間後執行；人工 `realtimeRun` 不更新排程時間，也未共用該 cache 檢查。這不是已驗證的跨副本原子鎖或持久佇列。

夜間「全量」必須明確對應要執行的 Plan／Trigger 與選取案例集合。單一 Plan 的 `selectedCaseNums` 不是整個資料庫全部案例；若有多個專案或計畫，外部編排需逐一追蹤其結果，辨識完成、失敗及未執行範圍。到固定截止時間仍強制關閉，不能因失敗、中斷、卡住或報告未完成而延後。

執行紀錄、最後進度及錯誤線索必須在截止前持續保存到關閉後仍可讀取的位置，不能只等最後一步集中寫回。若程序被強制停止，既有例外處理不保證能把 Run 改為 INTERRUPTED，資料庫可能仍留 RUNNING；早上啟動後須用截止時間、最後紀錄與實際執行程序對帳，再標示未完成原因。固定截止時間、關閉哪些資源及線索保存位置仍待部署規格確定；不新增未實作的停止 Run API。

### 2-2 完整案例儲存

| 提交內容 | 服務行為 | UI 要求 |
| :--- | :--- | :--- |
| `stepNum=null` | 新增步驟 | 不自行編造步驟 ID |
| `stepNum` 屬於本案例 | 更新步驟，保留識別 | 保留原有 ID |
| 舊步驟未出現在提交清單 | 刪除舊步驟 | 必須送完整清單，不能只送正在編輯的一步 |
| 非本案例的非空 `stepNum` | 不進入新增或更新分支 | 送出前擋下，不把其他案例 ID 混入 |
| 修改案例的 `caseType`、`suiteNum` | 修改 DTO 沒有這兩個欄位 | 不在一般儲存操作切換型別；搬群組用獨立 API |
| 新步驟備註 | 新增分支未保存 `stepRemark` | 不承諾新建時一定保留備註 |

新增／修改後重新載入案例，取得服務實際保存的步驟及順序。現況沒有 version／ETag 衝突檢查合約，兩人同時編輯可能互相覆蓋。

### 2-3 六種步驟與後處理

| stepType | 類型 | 主要契約與限制 |
| :--- | :--- | :--- |
| `1` | HTTP | `method`、`url`、`input`、`postProcPos`；`input.params`／`headers` 送空 object，Body 是字串 |
| `2` | SQL_COMPARE | 指定資料來源、schema、table、比對欄位、join／where 條件 |
| `3` | PRE_SQL | `qaSource`、`sqlScript`；可用 `resultToVarPos` 將結果存為變數 |
| `4` | SQL_TRANSFER | `qaSource`、`mode`、`source`、`target`；搬移測試資料 |
| `6` | INVOKE | 指定系統、類別、方法與參數；不是前端任意 JavaScript |
| `7` | SQL_SELECT_COMPARE | 預期 SQL 與實際 SQL 結果比對；現有多型序列化宣告尚有差異，需部署契約測試 |

型別 `5` 已停用。HTTP 支援狀態碼與 JSON 斷言；JSON expression 必須以 `$` 開頭，assertion 支援 `equals`。HTTP 與 INVOKE 的等待後處理雖有模型，執行 enum／switch 已停用。`HttpStepPo.output` 不參與現有 HTTP 判定。

HTTP 沒有後處理時，非 4xx／5xx 判為成功；有後處理時按啟用斷言結果判定。後處理依陣列順序走訪，不自行依 `order` 排序。Case 相依檢查目前只明確攔截 FAILED，而且查詢未限制本次 `runNum`；不能宣稱已實作「同次執行前置全部通過」。

## 3 防呆與錯誤矩陣

### 3-1 現有檢核與目標 RFC 9457 映射

現有 Crystal 錯誤格式為 `status:fail`、`errors[{code,message}]`，不是 RFC 9457。下表最後欄為待實作轉接格式的 `status`／`code`，保留已有 E998、E999 等代碼。Northstar 自身未套用 Crystal 錯誤模型。

| 檢查項目 | 觸發端點 | 已有條件與處置 | 現有結果 | 目標 RFC 9457 |
| :--- | :--- | :--- | :--- | :--- |
| 案例必要欄位 | QAC004 新增／修改 | 驗證 projectCode、名稱、必要 ID；修改 caseSteps 不可為 null | Bean Validation 失敗回 400，舊 code 可能為處理器識別 | 400／E998，欄位原因放 invalid_params |
| 群組存在與 hook 唯一 | QAC004 新增 | 群組需屬該專案／公司；同群組同類 hook 不可重複 | 400／E998 | 400／E998 |
| HTTP 步驟 | QAC004 新增／修改 | method、url 非空；後處理依型別檢查 expected／expression／assertion | 400／E998 | 400／E998 |
| HTTP JSON 比對 | QAC004 新增／修改 | expression 以 `$` 開頭、assertion 為 equals | 不符拒絕儲存，400／E998 | 400／E998 |
| SQL 步驟 | QAC004 新增／修改 | 各型別檢查資料來源、SQL、表名或比對條件 | 400／E998 | 400／E998 |
| INVOKE 步驟 | QAC004 新增／修改 | systemName、className、methodName；各參數 type／body | 400／E998 | 400／E998 |
| 已有未完成執行 | QAC006 invoke | 查到 NEW／RUNNING Run 時拒絕 | 400／E998；不是排隊成功 | 400／E998 |
| 觸發器或 source 缺失 | QAC006 invoke | 找不到 Trigger、source 或對應來源 URL | 一般例外，500／E999 | 500／E999；未補前置驗證前不改稱 400 |
| Trigger 關聯 | QAC006 新增／修改 | 計畫／環境、通知群組須存在；Cron 可解析 | 關聯缺失 400／E998；Cron 解析例外依處理器 | 保留現有狀態；先補明確欄位檢核 |
| 無法讀取 body／型別不符 | Crystal 管理 API | JSON 無法讀取或參數型別錯誤 | 400／E999 | 400／E999 |
| 不支援 Content-Type | Crystal API | 媒體型別不符 | 415／E001 | 415／E001 |
| Controller 存取拒絕 | Crystal API | 存取權限不符 | 401／E999；filter／gateway 另有格式 | 401／E999，不假定外層已統一 |
| 資料庫或一般例外 | Crystal API | 無法讀寫或未分類例外 | 500／E999 | 500／E999 |

檢核仍有缺口：`caseSteps` 沒有巢狀 `@Valid`；新增案例會直接迭代陣列；非 object 的 stepExec 並不走完整檢查分支；人工觸發 DTO 的 triggerNum 註記型別不合適且入口未加 `@Valid`。前端應擋下空值，但不能因此聲稱服務端已有一致的防呆。

### 3-2 RFC 9457 目標格式

目標錯誤媒體型別是 `application/problem+json`，`status` 與實際 HTTP 狀態一致；`type` 識別問題種類，`detail` 描述本次原因。`code`、`invalid_params` 為擴充欄位。此語意依據 [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html)。

本專案 profile 要求 `type`、`title`、`status`、`detail` 必填，比 RFC 的基本要求更嚴格。尚未有問題目錄時可用 `about:blank`；不建立虛假的錯誤文件 URL。錯誤不輸出憑證、SQL 密碼或完整堆疊。

OpenAPI 的 `responses` 描述現況；`x-target-error-response` 與 `ProblemDetails` 明列目標。客戶端現在不能假設服務會回標準 Problem Details。

## 4 逾時、重試與冪等性

### 4-1 已有設定與限制

| 呼叫鏈 | 現有 timeout | 既有防重能力與限制 |
| :--- | :--- | :--- |
| Crystal → Northstar 啟動 | 共用 HTTP client 未指定覆寫時 connect／socket timeout 均為 300000 ms | 啟動前查 NEW／RUNNING；查詢與建立 Run 不是原子操作 |
| Sentry → Crystal | Retrofit client read timeout 5 分鐘；其他 timeout 未在此設定覆寫 | 未見應用層指數退避與持久冪等鍵 |
| HTTP Step → 待測系統 | connect／取連線 timeout 5000 ms；預設 socket timeout 480000 ms | HTTP 動作可能有副作用，不能把整步重跑當成安全重試 |
| Northstar 排程觸發 | 以 triggerNum 對應 cache 檢查執行中 | 先查再寫，不保證跨副本互斥；人工入口沒有同一段檢查 |
| 結果回寫 | insert 與 update API 分開 | 已提交但回應遺失時，直接重送 insert 可能重複建立紀錄 |

socket／read timeout 是讀取等待限制，不等於整個 Run 的執行上限。`RETRY` 狀態存在，也不代表已有完整的自動重跑編排。

### 4-2 建議重試政策：待實作與驗收

| 情境 | 建議政策 |
| :--- | :--- |
| 定義／結果唯讀查詢 | 連線 3 秒、讀取 30 秒作為驗收起點；暫時性網路錯誤或 502／503／504 最多重試 3 次，等待 `random(0, min(8 秒, 0.5 秒 × 2^n))`，n 從 0 起 |
| 人工啟動逾時 | 先查 Trigger 對應執行紀錄及啟動時間，再查 Northstar 日誌；現況不自動重送。不要以「最新 Run」作唯一對帳依據 |
| 結果 insert 逾時 | 先以已知 Run／Case／Step 關聯確認是否寫入；未確認前不重送 |
| 業務檢核失敗 | 修正欄位、關聯或執行狀態後再提交，不以重試繞過 |
| HTTP／SQL／INVOKE 步驟失敗 | 先判斷待測動作是否已生效，保留原結果；只有已定義清理與資料重置程序的案例才重新執行 |

唯讀查詢包含部分 POST 查詢 API；方法名稱本身不能判定是否有副作用。Northstar GET／OPTIONS 啟動入口則有副作用，禁止當成一般 GET 自動重試或健康探測。

### 4-3 X-Idempotency-Key 目標

目前沒有 `X-Idempotency-Key` 處理、持久啟動請求識別或可靠的啟動回覆 runNum。下列設計是補強要求，不是現況功能。

```mermaid
sequenceDiagram
    participant U as 呼叫端
    participant C as 目標 Crystal 啟動協調
    participant R as Redis
    participant D as 持久請求紀錄
    participant N as Northstar
    U->>C: 既有 invoke 路徑及 UUIDv4 冪等鍵
    C->>R: SET 公司與端點與鍵 摘要 NX EX 600
    alt 首次請求
        C->>D: 記錄請求識別與啟動意圖
        C->>N: 啟動，攜帶可追蹤識別
        N-->>C: 回覆與 Run 的持久關聯
        C->>D: 保存已接受結果及 runNum
        C->>R: 保存結果 EX 86400
        C-->>U: 回覆同一執行識別
    else 同鍵同內容且完成
        C->>D: 讀取已接受結果
        C-->>U: 回放原執行識別，不再次啟動
    else 同鍵異內容或仍處理中
        C-->>U: 409 ProblemDetails，拒絕重複啟動
    else 回應遺失或鎖逾期
        C->>D: 查核既有啟動意圖及 Run 關聯
        C-->>U: 待確認，不盲目重送
    end
```

| 設計項目 | 目標規則 |
| :--- | :--- |
| 鍵格式 | UUIDv4；OpenAPI 已列版本與 variant 的 regex，尚未由服務端執行 |
| 隔離範圍 | 公司、HTTP 方法、端點與冪等鍵；摘要包含 projectCode、triggerNum 與本次執行選項 |
| 處理鎖 TTL | 初始 600 秒並續租；鍵過期不等於沒有啟動過 |
| 完成回應 TTL | 86400 秒；只存識別與結果摘要，不存密碼、token 或測試個資 |
| 持久紀錄 | 需新增請求與 Run 的穩定關聯，與接收流程一致保存；Redis 不是執行真相來源 |
| Redis 失效 | 新啟動保守拒絕或進待確認流程，不直接繞過防重 |
| 與現況相容 | 此方案需要調整 Crystal／Northstar 契約，不能只在 UI 多送一個 header |

## 5 白天與夜間的整合驗收

| 時點 | 必須確認 | 現況／需求界線 |
| :--- | :--- | :--- |
| 早上啟動 | EKS 與待測服務就緒，測試資料可用，再開放同仁 | 已確認需求；需由基礎設施編排提供就緒證據 |
| 半夜全量開始 | 同仁已停止操作，建立本輪 Plan／Trigger 清單，資料初始化可執行 | 共用環境與資料，不假設隔離 |
| 執行中 | 每個計畫有對應 Run；Case／Step 結果、最後進度與錯誤線索持續保存 | API 已有結果紀錄；本輪多 Run 關聯及關閉後仍可讀的保存位置需編排明確建立 |
| 夜間結果整理 | 區分已執行、失敗、中斷與未執行範圍；保存必要結果與可取得的附件 | FINISHED 不等於全通過；報告或附件未完成不能阻擋固定截止關閉 |
| 固定截止強制關閉 | 無論通過、失敗、中斷或卡住，到時仍關閉；關閉前必須已持續留下調查線索 | 不等待待寫回工作或報告無限完成；不把刪 Trigger 當停止 Run，也不杜撰取消 API |
| 早上重啟調查 | 讀取保留線索，對帳殘留 RUNNING 與未完成案例，確認本輪執行到哪一步 | 強制停止不保證最後狀態寫回；精確截止時間、關閉資源與保存位置待部署規格確定 |

詳細驗收見 [UAT 與驗證](06_UAT_AND_VERIFICATION.md)；逾時、未建立 Run 與結果中斷的處置見 [排障手冊](07_TROUBLESHOOTING_RUNBOOK.md)。
