# MySQL TaskStore node_executions 實作前確認

## 1. 文件目的

本文件用來啟動目標模式下的下一個第二階段工作節點：`MySQL TaskStore node_executions 第一版`。

本文件只做實作前確認，先固定欄位、索引、交易策略、測試範圍與停止線；不直接建立資料表、不執行 DDL、不連真實 DB、不修改 `ITaskStore` public method 簽章，也不修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public model。

## 2. 目前已具備能力

| 項目 | 狀態 | 說明 |
| --- | --- | --- |
| `task_executions` | 已完成第一版 PoC | 已完成 Save upsert、FindById / ListRecent、fake gateway 測試與真實 DB manual-only 驗證。 |
| `TaskExecutionRecord.NodeRecords` | 已完成 | 已可保存節點摘要，包含 NodeId、NodeName、DeviceId、CommandName、Status、RetryCount、TimeTakenMs、DataSummary、MetadataSummary、NextNodeId、TransitionSummary。 |
| `TaskExecutionRecord.Restore` | 已有 internal helper | 可供 Infrastructure.MySql 還原任務摘要，不新增 public API。 |
| `MySqlTaskStoreSqlBuilder` | 已完成第一版 | 目前只產生 `task_executions` SQL。 |
| `IMySqlTaskStoreGateway` | 已完成第一版 | 目前支援 non-query、single row、list row；仍以 fake gateway 測試為主。 |
| Schema Attribute / SQL Generator | 已完成第一輪 | 可用 C# class 表達 table / column / index / comment，並產生 MySQL 5.6.2 相容 SQL。 |

## 3. 本批目標

本批建議只做 `node_executions` 節點摘要持久化，目標如下：

1. 新增 `node_executions` schema class，讓欄位、索引與描述可由 C# 維護。
2. 擴充 MySQL TaskStore SQL builder，支援節點寫入 SQL。
3. `MySqlTaskStore.Save(record)` 保存 `task_executions` 後，同批保存 `record.NodeRecords` 摘要。
4. 使用 fake gateway 測試 SQL、參數、節點順序與敏感資訊遮蔽。
5. 不要求 `FindById` / `ListRecent` 第一批立即完整載入節點明細。
6. 真實 MySQL 寫入驗證另列 manual-only 驗證節點，需使用者確認 gate 後才執行。

## 4. 本批可以做

| 項目 | 是否可做 | 理由 |
| --- | --- | --- |
| 新增 `node_executions` schema class | 可以 | 只定義 expected schema，不直接執行 DDL。 |
| 新增節點 SQL builder method | 可以 | 屬 Infrastructure.MySql 內部 SQL 產生，不改 Core contract。 |
| 新增 node row / parameter mapper | 可以 | 由 `TaskNodeExecutionRecord` 轉成 MySQL 參數。 |
| 擴充 fake gateway 測試 | 可以 | 不連真實 DB，適合作為第一批安全驗證。 |
| `Save` 同批保存任務與節點摘要 | 可以 | 使用既有 `ITaskStore.Save(record)`，不新增 public method。 |
| 更新文件與實作紀錄 | 可以 | 讓文件 repo 後續可同步顯示完成狀態。 |

## 5. 本批暫不做

| 項目 | 暫不做原因 |
| --- | --- |
| 不修改 `ITaskStore` public method | 避免擴大 Core contract。 |
| 不修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public constructor 或 property | 現有節點摘要已足夠做第一版。 |
| 不保存 RawRequest / RawResponse | 這些資料目前最完整來源在 `WorkflowNodeExecutionSnapshot` / `LogEntry`，不屬本批。 |
| 不實作 `command_executions` | 命令明細資料來源尚未定案。 |
| 不實作 `task_log_traces` | 詳細 log 應由 DB LogWriter / TraceStore 承接，不塞進 TaskStore。 |
| 不接 ConsoleHost / ServiceHost 啟動流程 | 第二階段仍維持 Infrastructure 行為驗證，不導入正式 host。 |
| 不執行 `CREATE TABLE` / `ALTER TABLE` / `DROP TABLE` | DDL 屬高風險操作，需另行確認。 |

## 6. 建議資料表定位

| Table | 定位 | 第一版責任 |
| --- | --- | --- |
| `task_executions` | 任務主檔 | 任務狀態、Workflow、時間、結果摘要。 |
| `node_executions` | 節點摘要 | 任務內每個節點的執行順序、狀態、設備命令摘要與轉移摘要。 |

`node_executions` 第一版是 TaskStore 的明細表，不是詳細 log table。

## 7. 建議欄位

| 欄位 | 型別 | 必填 | 來源 | 描述 |
| --- | --- | --- | --- | --- |
| `task_id` | `varchar(64)` | 是 | `TaskExecutionRecord.TaskId` | 任務識別碼，與 `task_executions.task_id` 對應。 |
| `sequence_no` | `int` | 是 | `NodeRecords` 順序，由 1 開始 | 任務內節點執行順序。 |
| `node_id` | `varchar(64)` | 是 | `TaskNodeExecutionRecord.NodeId` | Workflow 節點識別碼。 |
| `node_name` | `varchar(128)` | 否 | `TaskNodeExecutionRecord.NodeName` | Workflow 節點名稱。 |
| `device_id` | `varchar(64)` | 否 | `TaskNodeExecutionRecord.DeviceId` | 節點使用的設備識別碼。 |
| `command_name` | `varchar(128)` | 否 | `TaskNodeExecutionRecord.CommandName` | 節點執行的設備命令名稱。 |
| `status` | `varchar(32)` | 是 | `TaskNodeExecutionRecord.Status` | 節點執行狀態。 |
| `retry_count` | `int` | 是 | `TaskNodeExecutionRecord.RetryCount` | 節點實際重試次數。 |
| `started_at` | `datetime` | 是 | `TaskNodeExecutionRecord.StartedAt` | 節點開始時間。 |
| `ended_at` | `datetime` | 否 | `TaskNodeExecutionRecord.EndedAt` | 節點結束時間。 |
| `time_taken_ms` | `int` | 是 | `TaskNodeExecutionRecord.TimeTakenMs` | 節點耗時毫秒數。 |
| `error_code` | `varchar(64)` | 否 | `TaskNodeExecutionRecord.ErrorCode` | 節點失敗時的錯誤代碼。 |
| `error_message` | `varchar(255)` | 否 | `TaskNodeExecutionRecord.ErrorMessage` | 節點失敗時的錯誤訊息。 |
| `data_summary` | `text` | 否 | `TaskNodeExecutionRecord.DataSummary` | 節點回傳 Data 摘要。 |
| `metadata_summary` | `text` | 否 | `TaskNodeExecutionRecord.MetadataSummary` | 節點 Metadata 摘要。 |
| `next_node_id` | `varchar(64)` | 否 | `TaskNodeExecutionRecord.NextNodeId` | 實際轉移的下一節點。 |
| `transition_summary` | `text` | 否 | `TaskNodeExecutionRecord.TransitionSummary` | 節點轉移判斷摘要。 |
| `created_at` | `datetime` | 是 | 寫入當下或 task updated time | 節點紀錄建立時間。 |

## 8. 建議主鍵與索引

第一版建議採複合主鍵：

```text
PRIMARY KEY (task_id, sequence_no)
```

理由：

- 不需要新增 Guid 或流水號產生器。
- 可直接表示同一任務內的節點順序。
- 搭配 delete-and-insert 寫入策略時語意清楚。

建議索引：

| Index | Columns | 用途 |
| --- | --- | --- |
| `idx_node_executions_task_id` | `task_id` | 查詢指定任務節點歷程。 |
| `idx_node_executions_status` | `status` | 查詢失敗或執行中節點。 |
| `idx_node_executions_device` | `device_id` | 查詢某設備參與的節點。 |
| `idx_node_executions_node_id` | `node_id` | 查詢特定 Workflow 節點的執行紀錄。 |

備註：若 `PRIMARY KEY (task_id, sequence_no)` 已能支援 `task_id` 查詢，`idx_node_executions_task_id` 可在實作時評估是否保留；若為了可視化與文件一致性，保留也可以接受。

## 9. 寫入策略

第一版建議採 `delete-and-insert`：

1. `MySqlTaskStore.Save(record)` 先 upsert `task_executions`。
2. 刪除同 `task_id` 既有 `node_executions`。
3. 依 `record.NodeRecords` 順序插入節點摘要。

理由：

- `TaskExecutionRecord.NodeRecords` 是目前完整節點摘要來源。
- 任務 Save 目前具備覆蓋語意；同一 `task_id` 重複保存時，節點明細也應反映最新狀態。
- 比逐筆 upsert 更容易避免舊節點殘留。

限制：

- 若未來需要保留歷史 Save 版本，`delete-and-insert` 不適合，需另行設計 version 或 event table。
- 真實 DB transaction 尚未實作前，不能讓刪除與插入中途失敗造成明細不一致。

## 10. 交易策略

第一版實作建議先拆成兩層：

| 階段 | 策略 |
| --- | --- |
| fake gateway 單元測試 | 驗證會依序建立 task upsert、node delete、node insert commands。 |
| 真實 DB manual-only | 需確認 gateway 是否以 transaction 包住 task upsert + node delete + node inserts。 |

若要實際導入 transaction，建議優先在 `MySqlTaskStoreGateway` 內新增 internal transaction method，不改 `ITaskStore`。

## 11. 查詢還原策略

第一版建議：

- `FindById` 可以維持只查 `task_executions`，不立即載入 `NodeRecords`。
- `ListRecent` 必須維持輕量，只查任務主檔。
- 節點明細查詢若要提供，建議後續新增 internal mapper 與獨立查詢設計，再評估是否擴充 public API。

原因：

- `ITaskStore.FindById` 目前沒有「是否包含明細」參數。
- `ListRecent` 若每筆都載入節點，未來資料量可能過大。
- 直接改 public API 屬於停止線，需使用者確認。

## 12. 測試策略

### 12.1 fake gateway 單元測試

| 測試 | 目的 |
| --- | --- |
| `Save_ShouldDeleteExistingNodeExecutionsBeforeInsert` | 確認同一 task_id 重存時先刪舊節點。 |
| `Save_ShouldInsertNodeExecutionsInOrder` | 確認 `sequence_no` 由 1 開始，順序符合 `NodeRecords`。 |
| `Save_ShouldMapNodeExecutionParameters` | 確認 NodeId、DeviceId、CommandName、Status、RetryCount、DataSummary、MetadataSummary、NextNodeId、TransitionSummary 被寫入參數。 |
| `Save_ShouldSkipNodeInsertWhenNoNodeRecords` | 沒有節點時只保存 task 主檔，不產生 node insert。 |
| `Save_ShouldTruncateNodeExecutionLongFields` | 確認 varchar 欄位不超過 DB 長度。 |
| `Save_ShouldNotExposePasswordWhenNodeCommandFails` | node command 失敗時錯誤訊息不得包含密碼。 |

### 12.2 schema / SQL 測試

| 測試 | 目的 |
| --- | --- |
| `NodeExecutionSchema_ShouldParseColumnsAndIndexes` | 確認 schema class 可解析欄位、主鍵、索引、comment。 |
| `NodeExecutionSchema_ShouldGenerateMySql56Sql` | 確認 SQL Generator 可產生 MySQL 5.6.2 相容 SQL。 |

### 12.3 manual-only 真實 DB 驗證

本批完成 fake gateway 後，真實 DB 驗證需另列節點，不自動執行。

建議驗證項目：

1. 使用測試 DB `hs_device_control_test`。
2. 確認 `node_executions` table 已由使用者建立或經確認後建立。
3. 以 manual-only gate 寫入一筆含兩個節點的 task。
4. 驗證 `task_executions` 有一筆主檔。
5. 驗證 `node_executions` 有兩筆節點明細。
6. 驗證 index / comment 可由 metadata inspector 讀出。
7. 驗證測試資料 cleanup。

## 13. 必須停下確認的事項

以下任一項出現時，Codex 必須停下，不得自動執行：

1. 修改 `ITaskStore` public method 簽章。
2. 修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public constructor 或 property。
3. 對真實 DB 執行 `CREATE TABLE`、`ALTER TABLE`、`DROP TABLE`。
4. 對真實 DB 執行未經確認的 `INSERT`、`UPDATE`、`DELETE`。
5. 把真實 DB 測試加入一般 CI。
6. 保存或輸出 password、完整 connection string、Token。
7. 把 `command_executions` 或詳細 log table 混進同一批。
8. 修改 ConsoleHost / ServiceHost / WebApi 啟動方式。
9. 新增正式背景服務或正式 API 邊界。

## 14. 建議實作順序

1. 新增 `NodeExecutionSchema` 或同等 schema class。
2. 補 schema parser / SQL generator 測試，確認欄位、索引與 comment。
3. 新增 `MySqlTaskNodeExecutionRow` 或 node parameter mapper。
4. 擴充 `MySqlTaskStoreSqlBuilder`，產生 delete nodes 與 insert node SQL。
5. 擴充 fake gateway，讓測試可檢查多個 command。
6. 修改 `MySqlTaskStore.Save`，保存 task 主檔後保存 node 摘要。
7. 補 fake gateway 單元測試。
8. 執行 `dotnet test tests\HS.DeviceControl.Infrastructure.MySql.Tests\HS.DeviceControl.Infrastructure.MySql.Tests.csproj`。
9. 補實作紀錄與下一個 manual-only 驗證設計。

## 15. 本次確認結論

建議採用「先保存節點摘要，不急著回讀明細」：

1. `node_executions` 第一版由 `TaskExecutionRecord.NodeRecords` 產生，不修改 Core public contract。
2. 主鍵建議採 `task_id + sequence_no`。
3. 寫入策略建議採 `task upsert + node delete-and-insert`。
4. 查詢第一版維持 task 主檔，節點明細查詢另行設計。
5. 真實 DB table 建立與 manual-only 寫入驗證必須另行確認。

建議下一步進入「MySQL TaskStore node_executions 第一版實作」，但只做 schema class、SQL builder、fake gateway 與單元測試；遇到 DDL、真實 DB 寫入或 public contract 變更時必須停下。
