# 3D Plugin / 多設備單元邊界分析

## 1. 文件定位

本文件是第三階段 3D「平台化擴充」的第一份邊界分析，用來釐清未來外部 Plugin DLL、多設備單元、資源鎖、命令佇列、設定版本與回滾之間的責任分工。

本文件只做分析與確認準備，不代表已同意新增 Plugin Loader、載入外部 DLL、修改 `IDeviceAdapter` public contract、導入外部 plugin framework、建立背景服務、修改設定檔格式、接真實硬體或把案場客製邏輯寫入 Core。

## 2. 3D 目標

3D 的目標不是立即讓系統動態載入外部 DLL，而是先把平台化擴充的邊界固定好，避免後續把設備單元、硬體 driver、資源排程、任務佇列、設定版本與案場客製流程混在同一層。

| 目標 | 說明 |
|---|---|
| 固定多設備單元模型 | 釐清 `ControlUnitId`、`StationId`、`DeviceId`、Adapter instance 與實體設備之間的關係。 |
| 固定 Plugin 邊界 | 決定未來外部 DLL 應提供什麼資訊、如何失敗隔離、如何映射到既有 Adapter contract。 |
| 固定 Resource Lock 邊界 | 避免多任務、多設備單元或多命令同時搶同一實體資源。 |
| 固定 Command Queue 邊界 | 讓命令排程與任務生命週期可追蹤，不在 Controller、Host 或 Adapter 內各自排隊。 |
| 固定設定版本與回滾方向 | 未來 JSON / plugin / 設備設定變更需可審核、追蹤與回復。 |
| 建立停止線 | 未確認前不載入 DLL、不改 public contract、不導入套件、不改啟動方式。 |

## 3. 既有前置能力

| 前置能力 | 狀態 | 對 3D 的意義 |
|---|---|---|
| `IDeviceAdapter` | 已存在 | 未來 plugin adapter 應先映射回既有 Adapter contract，不讓 Core 認識 DLL 載入細節。 |
| `DeviceId` | 已存在 | 目前已可由設定、WorkflowNode、DeviceCommand 與 Log 串接設備識別。 |
| `DeviceAdapterDispatcher` | 已存在 | 已具備依 `DeviceId` 分派 adapter 的基礎，但尚未處理多設備單元與資源鎖。 |
| Adapter Connect rollback | 已完成 | 多設備啟動中途失敗時，已成功連線的 adapter 會補償斷線，可作為未來 ControlUnit 啟動失敗策略基礎。 |
| `TaskEngine` / `WorkflowEngine` | 已存在 | Resource Lock / Command Queue 不應讓 Workflow node 自行散落管理。 |
| `HS.DeviceControl.Application` | 3C 已完成第一版 | 未來可承接任務控制、設備狀態、健康檢查與平台化協調，但 3D 本文件不修改程式。 |
| Log / Trace / TaskStore | 第二階段與 3A 已完成基礎 | 未來 lock、queue、plugin 載入與設備命令需可追蹤，不只回傳 bool。 |

## 4. 名詞邊界

| 名詞 | 建議語意 | 不應混淆的內容 |
|---|---|---|
| `ControlUnitId` | 一組可被系統共同管理的設備單元，例如一台調劑台、一組藥櫃、一個控制盒或一組產線 station。 | 不等於單一 adapter，也不一定等於一台 PC。 |
| `StationId` | Control Unit 底下可被操作或辨識的位置，例如站點、格位、通道、藥槽或工作區。 | 不應拿來取代 `DeviceId`。 |
| `DeviceId` | 系統內對單一設備或 adapter instance 的穩定識別。 | 不應包含連線密碼、IP、Port 等敏感資訊。 |
| Adapter instance | 程式執行時實際負責 `Connect`、`Disconnect`、`GetStatus`、`Execute` 的物件。 | 不應知道 WebApi route 或 UI 控制項。 |
| Plugin package | 未來可能由外部 DLL、manifest、版本資訊與相依檔案組成的設備擴充包。 | 本階段不載入、不掃描、不執行。 |
| Resource | 實體或邏輯上不能被同時占用的資源，例如設備、COM port、TCP endpoint、機械手臂、門鎖、秤重平台。 | 不只等於 `DeviceId`，也可能跨多個 device。 |

## 5. 多設備單元模型邊界

第一版分析建議先把多設備單元視為「設定與執行協調模型」，不要直接改既有 config schema。未來若要實作，需另行確認是否新增 `ControlUnitId` / `StationId` 欄位，以及它們如何映射到既有 `DeviceId`。

| 層級 | 候選責任 | 第一版邊界 |
|---|---|---|
| Control Unit | 管理一組設備、共享資源與啟停生命週期。 | 先分析，不新增設定欄位。 |
| Station | 表示 Control Unit 內的一個操作位置或槽位。 | 先定義語意，不綁定特定藥局設備。 |
| Device | 既有 adapter 操作單位。 | 沿用 `DeviceId`，不改 Adapter contract。 |
| Workflow Node | 宣告要操作的 `DeviceId` 與命令。 | 不在 node 內寫死連線資訊或資源鎖實作。 |
| Task / Command | 任務與命令需可追蹤 lock / queue 狀態。 | 未確認前不新增資料表或 DB 欄位。 |

建議原則：

- `ControlUnitId` 用來描述「哪一組設備單元」，`DeviceId` 用來描述「哪一個設備 adapter」。
- `StationId` 可以輔助描述「哪個位置」或「哪個工作區」，但不應讓 Core 寫入案場客製語意。
- 同一個 `DeviceId` 不應同時被兩個任務任意操作，除非後續明確定義可共享模式。
- 跨設備的實體資源，例如同一條輸送機或同一個 COM port，應以 Resource Lock 管理，而不是靠命名慣例暗示。

## 6. Plugin Loader 邊界

Plugin Loader 未來應是「Infrastructure / Host 端能力」，不應進入 Core。Core 只需要知道 `IDeviceAdapter` 與標準 Result / Error，不能知道 DLL 路徑、AssemblyLoadContext、manifest、檔案雜湊或外部套件相依。

| 項目 | 邊界說明 |
|---|---|
| Plugin 掃描 | 未來可由 Host / Infrastructure 掃描指定目錄，但本階段不掃描。 |
| DLL 載入 | 未來需有 allowlist、版本、相依套件與失敗隔離策略，但本階段不載入。 |
| Adapter 建立 | Plugin 最終應產生或註冊 `IDeviceAdapter` 相容物件，不讓 WorkflowEngine 依賴 plugin 型別。 |
| Manifest | 可候選記錄 plugin id、版本、支援 device type、capability 與 checksum，但本階段不定 schema。 |
| 失敗隔離 | 載入失敗、版本不符、建構失敗、Connect 失敗需轉為標準 ErrorInfo / ExecuteResult。 |
| 安全 | 不保存密碼、Token、正式路徑或連線資訊到 repo；不從任意路徑載入 DLL。 |

Plugin Loader 不應負責：

- 不寫 Workflow node 邏輯。
- 不直接修改 task state。
- 不繞過 `IDeviceAdapter`。
- 不把案場流程寫入 Core。
- 不在未確認前導入外部 plugin framework。
- 不把 DLL 載入失敗吞掉或只回傳 `false`。

## 7. Resource Lock 邊界

Resource Lock 的目的，是保護不能同時被多個任務或命令占用的實體資源。它應該是任務執行協調層的能力，不應散落在每個 Adapter 或 Workflow node 內。

| 候選資源 | 鎖定原因 | 第一版建議 |
|---|---|---|
| `DeviceId` | 同一設備同時下多個命令可能造成狀態錯亂。 | 預設可視為 exclusive resource。 |
| 通訊通道 | 多個設備共用同一 COM port、TCP endpoint 或 gateway。 | 需允許跨 device 的 shared resource key。 |
| 機械動作區 | 馬達、門、輸送帶、秤重平台等實體動作不可重疊。 | 不寫死藥局語意，以 resource key 表示。 |
| Control Unit | 整組設備啟停、維護或急停時需整體鎖定。 | 未來可支援 unit-level lock。 |
| Schema Apply / Preview | Preview 可 read-only；Apply 屬高風險獨占操作。 | 3D 不處理正式 Apply。 |

Resource Lock 第一版候選欄位：

| 欄位 | 用途 |
|---|---|
| `ResourceKey` | 資源穩定識別，例如 `device:light-01` 或 `channel:com3`。 |
| `Mode` | `Exclusive` / `SharedRead` 等候選模式，需另行確認。 |
| `OwnerTaskId` | 鎖的任務擁有者。 |
| `OwnerNodeId` | 鎖的節點來源。 |
| `CommandName` | 觸發鎖的命令。 |
| `AcquiredAtUtc` | 取得時間。 |
| `ExpiresAtUtc` | 超時保護，避免永久卡住。 |
| `ReleaseReason` | 正常完成、失敗、取消、timeout 或補償釋放。 |

## 8. Command Queue 邊界

Command Queue 的目的，是讓命令進入可觀察、可取消、可追蹤的排程流程，而不是讓 HTTP request、Host loop、Adapter 或 Thread.Sleep 各自形成隱性佇列。

| 面向 | 邊界說明 |
|---|---|
| Queue 所在層 | 建議放在 Application / ServiceHost 協調層，不放 Core，不放 WebApi controller。 |
| Queue 單位 | 可依 `DeviceId`、`ResourceKey`、`ControlUnitId` 或 Task scope 決定。 |
| 排序策略 | 第一版建議先分析 FIFO；Priority、deadline、preemption 需另行確認。 |
| 狀態 | 至少需能區分 queued、running、completed、failed、cancelled、timeout。 |
| 錯誤 | queue 滿、lock timeout、device offline、adapter exception 需有標準錯誤碼。 |
| DB | 未確認前不新增 queue table，也不改 TaskStore schema。 |

Command Queue 不應負責：

- 不直接操作硬體，仍需透過 Adapter。
- 不把任務狀態改出 StateMachine 規則。
- 不在未確認前導入外部 queue 套件。
- 不把長時間流程塞回單一 WebApi request。

## 9. 設定版本與回滾邊界

多設備與 plugin 會讓設定變更風險升高，因此後續需要可追蹤的設定版本策略。但本文件不修改 `devices.json`、`workflows.json` 或 `appsettings.json` 格式。

| 項目 | 候選方向 | 本次邊界 |
|---|---|---|
| 設定版本 | 版本號、異動時間、作者、摘要、hash。 | 只分析，不改 schema。 |
| Plugin 版本 | plugin id、version、capabilities、checksum。 | 只分析，不建立 manifest。 |
| 相容性 | min core/app version、adapter contract version。 | 不改 public contract。 |
| 回滾 | 保留上一版設定與安全回復策略。 | 不建立儲存機制。 |
| 審核 | 正式變更前需人工確認與驗證節點。 | 不導入權限系統。 |

## 10. 與 3C Application Contract 的關係

3C 已建立 Application contract 第一版，但 3D 不應直接把 plugin 與 queue 塞進既有 contract。比較安全的順序是先完成 3D 邊界決策，再判斷是否需要新增 Application 層 contract。

| 既有 3C 能力 | 3D 可能承接方式 | 是否本次實作 |
|---|---|---|
| `DeviceStatusService` | 未來可查 Control Unit / Plugin adapter 狀態。 | 否 |
| `TaskControlService` | 未來可把命令排入 queue 或取得 lock 後執行。 | 否 |
| `HealthService` | 未來可回報 plugin、queue、resource lock 健康狀態。 | 否 |
| `SchemaPreviewService` | 與 plugin 無直接關係；仍只 preview、不 apply。 | 否 |

## 11. 候選程式影響範圍

以下只是後續取得明確授權後可能進入的候選範圍；本次不修改程式 repo。

| 類型 | 候選路徑 / 名稱 | 用途 | 是否已授權 |
|---|---|---|---|
| 新增文件 | `docs/plugin-multi-device-boundary-analysis.md` | 本次 3D 邊界分析 | 已授權進入文件整理 |
| 新增 contract | `HS.DeviceControl.Application.Resources/*` | Resource Lock request / result / service contract | 尚未 |
| 新增 contract | `HS.DeviceControl.Application.Commands/*` | Command Queue request / result / service contract | 尚未 |
| 新增模型 | `ControlUnitId` / `StationId` DTO | 多設備單元識別 | 尚未 |
| 新增 Plugin contract | `HS.DeviceControl.Plugins` 或 Application plugin namespace | Plugin metadata / factory 候選 | 尚未 |
| 新增 Infrastructure | Plugin Loader / manifest reader | 掃描與載入外部 DLL | 尚未，且需再次確認 |
| 修改 Adapter contract | `IDeviceAdapter` | 若需 capability / metadata 擴充 | 尚未，且目前不建議先改 |
| 修改設定 schema | devices / workflows / appsettings | Control Unit、Station、resource key、plugin mapping | 尚未 |

## 12. 驗證節點候選

| 驗證節點 | 驗證目的 | 前置條件 |
|---|---|---|
| 多設備單元模型確認 | 確認 `ControlUnitId` / `StationId` / `DeviceId` 不混淆，且不寫案場客製語意。 | 完成 3D 決策確認。 |
| Resource Lock dry-run 驗證 | 確認同一 resource 被第二個任務占用時會被擋下，且逾時可釋放。 | 需另行同意 contract / fake 實作。 |
| Command Queue fake 驗證 | 確認命令可排隊、狀態可查、取消可追蹤，不直接操作硬體。 | 需另行同意 queue contract。 |
| Plugin metadata 驗證 | 確認 manifest / metadata 可讀，但不載入 DLL。 | 需另行同意 metadata schema。 |
| Plugin Loader 隔離驗證 | 確認載入失敗會回標準錯誤且不影響 Host。 | 需另行同意載入外部 DLL，非本階段預設。 |

## 13. 尚待決策

| 決策 | 影響 |
|---|---|
| 是否採用 `ControlUnitId` / `StationId` 作為標準命名 | 會影響設定格式、DTO、Log、Trace 與 UI 顯示。 |
| Resource Lock 放在 Application 層、ServiceHost 層或 Infrastructure 層 | 會影響 public contract、測試與未來 DB 保存方式。 |
| Command Queue 是否先做 in-memory fake contract | 會影響 TaskControlService 與任務生命週期。 |
| Plugin 第一版是否只做 metadata / manifest，不載入 DLL | 可降低安全與相依套件風險。 |
| 是否維持 `IDeviceAdapter` 不變，先用 wrapper / factory 映射 plugin | 會影響 Adapter contract 穩定性。 |
| 是否需要設定版本與回滾文件先行 | 會影響 JSON schema 與管理流程。 |
| 是否將 3D 第一版限制為文件與決策確認 | 可避免過早導入外部 DLL、queue 套件或背景服務。 |

## 14. 停止線

本文件完成後，以下項目仍不得自動執行：

- 不載入外部 DLL。
- 不掃描 plugin 目錄。
- 不新增 Plugin Loader 程式。
- 不新增或更換 plugin framework、queue framework、DI / Host framework。
- 不修改 `IDeviceAdapter`、Core、Adapters、Infrastructure public method 簽章。
- 不修改既有 config schema 或啟動方式。
- 不新增 ServiceHost / WebApi 專案。
- 不新增 API route、controller、endpoint 或 middleware。
- 不執行 DB 寫入、DDL、ALTER TABLE 或正式 Apply。
- 不保存密碼、Token、IP、Port、完整 connection string、正式 DLL 路徑或正式環境資訊。
- 不把案場客製流程、藥局專用規則或特定設備邏輯寫入 Core。

## 15. 建議下一步

建議下一步先整理「3D 決策確認表」，讓使用者逐項確認：

1. 是否同意 3D 第一版先做邊界與 contract 草案，不載入外部 DLL。
2. 是否同意多設備單元命名先採 `ControlUnitId` / `StationId` / `DeviceId` 三層語意。
3. 是否同意 Resource Lock 第一版先做 Application 層 contract 候選，不新增 DB table。
4. 是否同意 Command Queue 第一版先做 fake / in-memory contract 候選，不導入 queue 套件。
5. 是否同意 Plugin 第一版先做 metadata / manifest 邊界，不建立 Loader。
6. 是否同意維持 `IDeviceAdapter` public contract 不變，後續用 wrapper / factory 承接 plugin。
7. 是否同意未確認前不改 config schema、不改啟動方式、不新增 ServiceHost / WebApi、不保存敏感資訊。
