# MySQL DB LogWriter / TraceStore 邊界設計

## 1. 文件目的

本文件承接 [MySQL TaskStore command_executions 實作前確認](mysql-taskstore-command-executions-preimplementation-checklist.md)，整理詳細診斷資料是否應由 `DB LogWriter / TraceStore` 承接，而不是直接塞入 `MySqlTaskStore.Save(record)`。

本文件只做邊界設計，不新增 table、不執行 DDL、不連真實 DB、不修改 `ITaskStore` / `ILogWriter` public method 簽章，也不修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public constructor 或 property。

## 2. 目前已完成狀態

| 項目 | 狀態 | 說明 |
| --- | --- | --- |
| `task_executions` | 已完成 | `MySqlTaskStore` 已可保存任務主檔，並完成真實 DB manual-only 驗證。 |
| `node_executions` | 已完成 | `MySqlTaskStore` 已可保存節點摘要，並完成真實 DB manual-only 驗證。 |
| `command_executions` | 尚未實作 | 已完成實作前確認，結論是不宜在資料來源未固定前直接塞入 TaskStore。 |
| `LogEntry` | 已存在 | 已包含 TaskId、NodeId、DeviceId、CommandName、RawRequestSummary、RawResponseSummary、AdapterTimeTakenMs、DataSummary、MetadataSummary、ErrorCode、Message。 |
| `ILogWriter` | 已存在 | 目前可由 Console / File / Memory writer 接收 `LogEntry`。 |
| `WorkflowEngine` | 已承接 Adapter Data | 已把 Adapter result 的 RawRequest / RawResponse / Data / Metadata 轉成 summary 寫入 `LogEntry`。 |

## 3. 核心邊界結論

`MySqlTaskStore` 與 DB diagnostic log 應拆開。

| 元件 | 職責 | 不應負責 |
| --- | --- | --- |
| `MySqlTaskStore` | 保存任務主檔與節點摘要，例如 `task_executions`、`node_executions`。 | 不保存完整診斷 log、不承擔 Raw payload 清理、不當作一般 log sink。 |
| `MySqlLogWriter` | 將 `LogEntry` 安全轉成 DB trace row。 | 不改變 Workflow 結果、不取代 Console / File log、不直接執行 schema apply。 |
| `TaskTraceStore` | 提供依 TaskId、NodeId、DeviceId、時間區間查詢 trace 的讀取介面。 | 不負責任務狀態轉換、不寫入 Workflow 核心狀態。 |
| `task_log_traces` | 保存任務與設備命令相關診斷摘要。 | 不替代 `task_executions` / `node_executions` / `command_executions`。 |

## 4. 為什麼建議走 Log 流

`command_executions` 若要保存 RawRequest / RawResponse，最完整的資料目前已在 `LogEntry` 與 Workflow snapshot 流中。

走 `LogEntry -> MySqlLogWriter -> task_log_traces` 的優點：

1. 不需要為了診斷欄位修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public model。
2. 不會讓 `MySqlTaskStore.Save(record)` 同時承擔任務保存與大量 log sink。
3. 可以獨立做 payload 遮蔽、長度限制、保留天數與查詢索引。
4. DB log 寫入失敗時，可獨立處理，不必改變 Workflow 任務成功或失敗判斷。
5. 未來若一個 node 執行多個 Adapter command，Log 流比 TaskStore node summary 更適合承接多筆診斷事件。

## 5. 建議資料流

```text
WorkflowEngine
  -> LogEntry
  -> ILogWriter
      -> ConsoleLogWriter
      -> FileLogWriter
      -> MemoryLogWriter
      -> MySqlLogWriter
            -> task_log_traces

TaskTraceStore
  -> query task_log_traces by TaskId / NodeId / DeviceId / time range
```

第一版建議 `MySqlLogWriter` 是可選 writer，不得取代既有 Console / File / Memory writer。

## 6. 建議 table 定位：task_log_traces

`task_log_traces` 應定位為「診斷 trace table」，不是任務主檔，也不是命令執行表。

### 6.1 欄位草案

以下只是設計草案，尚未確認前不得建立 table。

| 欄位 | 型別 | 必填 | 來源 | 說明 |
| --- | --- | --- | --- | --- |
| `trace_id` | `varchar(64)` | 是 | 寫入端產生 | Trace 主鍵，避免依賴 DB 自動編號造成測試不易比對。 |
| `task_id` | `varchar(64)` | 否 | `LogEntry.TaskId` | 任務識別碼。 |
| `node_id` | `varchar(64)` | 否 | `LogEntry.NodeId` | Workflow 節點識別碼。 |
| `node_name` | `varchar(128)` | 否 | `LogEntry.NodeName` | Workflow 節點名稱。 |
| `device_id` | `varchar(64)` | 否 | `LogEntry.DeviceId` | 設備識別碼。 |
| `command_name` | `varchar(128)` | 否 | `LogEntry.CommandName` | Adapter 命令名稱。 |
| `level` | `varchar(32)` | 是 | `LogEntry` 或 writer mapping | Log level，第一版可先以 Info / Warning / Error 表示。 |
| `status` | `varchar(32)` | 否 | `LogEntry.Status` | 任務、節點或命令狀態。 |
| `retry_count` | `int` | 否 | `LogEntry.RetryCount` | 節點 retry count。 |
| `time_taken_ms` | `int` | 否 | `LogEntry.TimeTakenMs` | 節點或事件耗時。 |
| `adapter_time_taken_ms` | `int` | 否 | `LogEntry.AdapterTimeTakenMs` | Adapter 命令耗時。 |
| `error_code` | `varchar(64)` | 否 | `LogEntry.ErrorCode` | 錯誤代碼。 |
| `message` | `varchar(255)` | 否 | `LogEntry.Message` | 訊息摘要。 |
| `data_summary` | `text` | 否 | `LogEntry.DataSummary` | Adapter Data 摘要。 |
| `raw_request_summary` | `text` | 否 | `LogEntry.RawRequestSummary` | 已遮蔽與截斷後的 request 摘要。 |
| `raw_response_summary` | `text` | 否 | `LogEntry.RawResponseSummary` | 已遮蔽與截斷後的 response 摘要。 |
| `metadata_summary` | `text` | 否 | `LogEntry.MetadataSummary` | Metadata 摘要。 |
| `host_name` | `varchar(128)` | 否 | Host context | 執行主機名稱。 |
| `operator_id` | `varchar(64)` | 否 | Host context | 操作者或服務帳號。 |
| `app_version` | `varchar(64)` | 否 | Host context | 程式版本。 |
| `created_at` | `datetime` | 是 | 寫入當下 | Trace 建立時間。 |

### 6.2 建議索引

| Index | Columns | 用途 |
| --- | --- | --- |
| `idx_task_log_traces_task_id` | `task_id` | 查詢指定任務完整 trace。 |
| `idx_task_log_traces_node_id` | `node_id` | 查詢特定節點 trace。 |
| `idx_task_log_traces_device` | `device_id` | 查詢設備相關 trace。 |
| `idx_task_log_traces_command` | `command_name` | 查詢命令相關 trace。 |
| `idx_task_log_traces_status` | `status` | 查詢失敗或特定狀態 trace。 |
| `idx_task_log_traces_error_code` | `error_code` | 查詢特定錯誤。 |
| `idx_task_log_traces_created_at` | `created_at` | 依時間排序與清理。 |

## 7. MySqlLogWriter 寫入策略

第一版建議：

1. `MySqlLogWriter` 只接收已產生的 `LogEntry`，不主動改 Workflow 狀態。
2. 寫入前必須經過 payload sanitizer，避免保存 password、token、connection string 或完整敏感 payload。
3. `RawRequestSummary` / `RawResponseSummary` 只保存摘要，不保存未遮蔽完整原文。
4. 單筆寫入失敗時，不應讓 Workflow 任務結果由成功變失敗；但必須讓 host 或 fallback log 知道 DB log 寫入失敗。
5. 第一版不做批次寫入、不做背景佇列、不做 retry worker，避免擴大到背景服務範圍。
6. 第一版不自動建表，也不在 ConsoleHost 啟動時 apply schema。

## 8. TaskTraceStore 查詢策略

`TaskTraceStore` 建議作為讀取介面，不與 `ITaskStore` 混在一起。

建議第一版查詢能力：

| 查詢 | 用途 |
| --- | --- |
| `FindByTaskId(taskId)` | 查詢單一任務完整 trace。 |
| `FindByNodeId(taskId, nodeId)` | 查詢指定任務節點 trace。 |
| `FindByDeviceId(deviceId, from, to)` | 查詢設備在時間區間內的 trace。 |
| `ListRecentErrors(limit)` | 查詢最近錯誤 trace。 |

以上查詢介面若要實作，會新增 public contract，必須另做實作前確認。

## 9. 與 command_executions 的關係

`task_log_traces` 與 `command_executions` 不應互相取代。

| Table | 建議定位 |
| --- | --- |
| `command_executions` | 給報表或任務明細查詢使用的命令執行表，欄位較固定。 |
| `task_log_traces` | 給除錯與診斷使用的 log / trace 表，資料量較大，欄位偏診斷。 |

目前建議先做 `task_log_traces` 邊界設計，是因為 RawRequest / RawResponse 較接近診斷資料，不宜為了它先修改 TaskStore model。

若後續需要 `command_executions`，可以從 `LogEntry` 或 `WorkflowNodeExecutionSnapshot` 派生，但必須先做資料來源決策表。

## 10. Payload 安全規則

DB trace 不能直接保存未處理的 raw payload。

第一版 sanitizer 至少應處理：

| 規則 | 說明 |
| --- | --- |
| 長度限制 | request / response summary 建議限制在固定字數，例如 2,000 或 4,000 字內。 |
| 敏感字遮蔽 | password、token、secret、connection string、authorization 需遮蔽。 |
| 二進位摘要 | 若 payload 是 byte array，只保存 hex 摘要與長度，不保存完整大 payload。 |
| Null 安全 | 沒有 raw data 時保存 null，不硬塞空字串。 |
| 錯誤可讀性 | 遮蔽後仍需保留足夠資訊判斷命令、裝置與錯誤原因。 |

## 11. 測試策略

### 11.1 自動測試

| 測試 | 目的 |
| --- | --- |
| `LogEntry` to trace row mapper 測試 | 確認欄位來源正確，Raw summary 不遺失。 |
| payload sanitizer 測試 | 確認敏感資訊被遮蔽，長字串會截斷。 |
| SQL generator 測試 | 確認 `task_log_traces` schema class 可產生 MySQL 5.6.2 DDL preview。 |
| fake gateway writer 測試 | 確認 `MySqlLogWriter` 寫入正確 SQL 與參數，不連真實 DB。 |
| writer failure 測試 | 確認 DB log 寫入失敗不會改變 Workflow result。 |

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

若後續完成實作，真實 DB 驗證需另列節點，且只能在使用者明確確認後執行：

1. 確認測試 DB 與 table 已準備完成。
2. 明確開啟 manual-only gate。
3. 寫入成功、失敗、含 Raw summary、含 ErrorCode 的 trace rows。
4. 依 TaskId 查回 trace。
5. 驗證 index 與 comment。
6. 驗證敏感資訊未被保存。
7. cleanup 僅清除本次測試前綴資料。

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

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

1. 新增或修改 public contract，例如 `ILogWriter`、`ITaskStore`、`TaskTraceStore` 介面。
2. 修改 `TaskExecutionRecord` / `TaskNodeExecutionRecord` public constructor 或 property。
3. 建立 `task_log_traces` table 或執行任何 DDL / ALTER。
4. 對真實 DB 執行未確認的 `INSERT`、`UPDATE`、`DELETE`。
5. 把真實 DB 測試加入一般 CI。
6. 保存 password、Token、完整 connection string 或未遮蔽 RawRequest / RawResponse。
7. 導入背景佇列、ServiceHost、WebApi 或正式 DB 自動 apply 流程。
8. 新增或更換 MySQL 套件。
9. 讓 DB log 寫入失敗直接改變 Workflow 任務成功 / 失敗結果。

## 13. 建議結論

建議採用「TaskStore 保存任務與節點，DB LogWriter 保存診斷 trace」的切分。

目前不要把 `command_executions` 或詳細 Raw 診斷資料直接塞進 `MySqlTaskStore.Save(record)`；較安全的下一步是做「DB LogWriter / TraceStore 實作前確認」，先固定：

1. `task_log_traces` schema class。
2. `LogEntry` to trace row mapper。
3. payload sanitizer 規則。
4. fake gateway 測試策略。
5. DB log 寫入失敗時的 fallback 行為。

確認後再進入第一版實作，仍維持不自動連真實 DB、不自動建表、不修改現有 TaskStore public contract。
