專案受評範圍合併(Audit Scope Consolidation)— 設計文件

狀態:設計確認中|範圍:Frontend only|估時:3-5 天 建立日期:2026-04-25|作者:Raymond + Claude

1. 需求說明

「資訊系統 (Information System, IS)」與「設備 (Device)」目前在兩個專案設定畫面裡分別佔據獨立的步驟 / Tab。從 user 視角來看,兩者都是「這個專案要稽核的範圍」之組成元件,分散在不同 step / tab 造成認知負擔。

本次需求:將兩者在 UI 層合併到單一區塊「受評範圍」,但保留現有資料模型與後端不動,作為將來 OSCAL SSP 匯出 / 跨模組引用的階段性踏腳石。

涉及的兩個畫面

路由 檔案
/project/projects/new src/views/project/ProjectCreateView.vue
/project/projects/:projectUID/settings src/views/project/ProjectSettingsView.vue

2. 目標 & 非目標

✅ 目標

  • 把 IS 和 Device 的 picker 合併到一個 section「受評範圍」(Audit Scope)
  • ProjectSettings:4 tabs → 3 tabs
  • ProjectCreate:5 steps → 4 steps,順序調整為「基本資訊 → 參與者 → 受評範圍 → 確認」
  • 兩個畫面共用一個新元件 AuditScopeSection.vue(包裝既有 SystemPicker + DevicePicker)
  • 不影響任何既有功能 / API / DB 行為

❌ 非目標(明確排除)

  • 不動 BE:update_project API、PUT /api/1.0/grc-project/<id> request shape、各 service 全部維持
  • 不動 schema:information_systemsdevicesproject_information_systemsproject_device_mappingjob_execution_device_mappingevidence 全部不動
  • 不動既有元件本身:SystemPicker.vue / DevicePicker.vue / DeviceManage.vue / InformationSystemManage.vue
  • 不動 jedi-device 套件 / jedi_information_system 模組
  • 不引入 ComponentService / ComponentPicker / FK 關聯
  • 不做 OSCAL SSP exporter
  • 不動 device 相關的高耦合區(job_execution_device_mapping、evidence.ref_id、device_view 聚合 SQL)

3. 設計選型過程

3.1 評估的方案

針對「合併」二字,討論過 5 個層次的方案:

方案 描述 估時 風險
A 大合併:JSONB 統一 components 表,廢掉 IS/Device 4-6 週 高(影響 job、evidence、跑中流程)
B 統一 component + 多表繼承(基底 + type 子表) 3-4 週 中高
C UI 合併、資料分開、加 system-device-mapping 多對多 2 週
D 兩表保留 + 加 device.information_system_id FK + ComponentService 抽象 + 樹狀 ComponentPicker 1.5-2 週
E 純 UI 整併,BE / schema 全不動 3-5 天 極低

3.2 為什麼選 E

關鍵發現(透過外部 reference 掃描):

  1. Device 在 runtime 重度耦合

    • job_execution_device_mapping — job 完成判定要 device 全完成
    • evidence.ref_id == device_uid — 證據檔靠 device_uid 分類
    • 有獨立 device_view 複雜聚合 SQL(job × device × evidence × subtask × assignee)
  2. IS 相對輕量:只在 project 啟動時做一次性 OSCAL SSP system_characteristic 快照,settings 改 IS 不會影響跑中的 job

  3. 存檔流程超簡單project_service.update_project 對 audit_systems / devices 都是 M2M replace,沒有任何 cascade。UI 怎麼合都不會打壞 BE

  4. OSCAL SSP 匯出是 future:用戶確認 Phase 1 不做匯出。匯出時可選 (a) 純讀現有表 (b) 加 mapping 小表 (c) 加 FK 到 device — 三條路在 Phase 1 都不需 lock-in

  5. E → 任一條 Phase 2 路線的 throwaway 小:picker UI 元件約 3-5 天會被換掉,但 tab 結構、版面、route 都能保留

3.3 Phase 2 方向(不在本次計畫)

當未來決定要做 OSCAL SSP 匯出時,可選擇:

  • (a) 純讀既有表:直接 query information_systems + devices,產出兩個獨立的 OSCAL component / inventory-item 區塊,不改 schema
  • (b) 加 mapping 小表:新增 device_system_mapping(device_id, system_id),只在匯出時用,不動既有表
  • (c) 加 FK 到 device + ComponentService(D 方案完整版):device.information_system_id + 樹狀 ComponentPicker + UI 表達層級

Phase 1 的設計對以上三條路徑都中立。

4. 系統 / 邏輯架構

ProjectCreateView.vue           ProjectSettingsView.vue
       │                                 │
       └────────────┬────────────────────┘
                    │
                    ▼
         AuditScopeSection.vue  ◄── 新元件(本次新增)
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
SystemPicker.vue (既有)    DevicePicker.vue (既有)
        │                       │
        ▼                       ▼
   API.INFORMATION_       API.DEVICES /
   SYSTEMS_MENU           menuStore.deviceMenu

存檔仍走既有 API:

SystemPicker.@change → syncProjectSystems()
   → PUT /api/1.0/grc-project/<id>  body: { audit_systems: [...] }

DevicePicker.@change → syncProjectDevices()
   → PUT /api/1.0/grc-project/<id>  body: { devices: [...] }

兩支 sync function 維持獨立呼叫(不做合併呼叫)。

5. UI 變更明細

5.1 ProjectSettingsView:4 tabs → 3 tabs

變更前 變更後
Tab 1: 基本資訊 Tab 1: 基本資訊(不動)
Tab 2: 稽核系統 (n) 🆕 Tab 2: 受評範圍 (n 系統 / m 設備)
Tab 3: 參與者 (n) Tab 3: 參與者(不動)
Tab 4: 設備 (n) (刪除,內容搬到 Tab 2)

Tab header 計數:分開顯示「3 系統 / 5 設備」格式(不用加總)。

5.2 ProjectCreateView:5 steps → 4 steps

變更前 變更後
Step 0: 基本資訊 Step 0: 基本資訊(不動)
Step 1: 稽核系統 (移到 Step 2)
Step 2: 參與者 Step 1: 參與者(位置往前)
Step 3: 設備 (移到 Step 2)
Step 4: 確認 🆕 Step 2: 受評範圍 → Step 3: 確認

確認頁區段:原本 4 區段(基本 / 系統 / 參與者 / 設備)→ 3 區段(基本 / 參與者 / 受評範圍)。受評範圍區段內部展開兩個子區塊:「資訊系統」+「設備」。

5.3 新元件:AuditScopeSection.vue

位置src/components/grc/AuditScopeSection.vue

版面(上下堆疊,stacked):

┌─ 受評範圍 ───────────────────────────┐
├─────────────────────────────────────┤
│ 📦 資訊系統  [+ 新增] [搜尋...]        │
│   • 人事系統                         │
│   • 財務系統                         │
├─────────────────────────────────────┤
│ 💻 設備  [+ 新增] [搜尋...]           │
│   • ap-server-01                    │
│   • db-server-01                    │
└─────────────────────────────────────┘

Props

interface Props {
  // System
  selectedSystemIds: string[]
  selectedSystemDetails: Record<string, any>
  infoSystemMenu: { id: string; name: string; abbreviation: string | null }[]
  // Device
  selectedDevices: string[]
  selectedDeviceDetails: Record<string, any>
  deviceMenu: any[]
  // Shared
  userMenu: any[]
  readonly?: boolean
  confirmBeforeRemove?: boolean
}

Emits

defineEmits<{
  // System
  'update:selectedSystemIds': [string[]]
  'update:selectedSystemDetails': [Record<string, any>]
  'update:infoSystemMenu': [{...}[]]
  'change-systems': []
  // Device
  'update:selectedDevices': [string[]]
  'update:selectedDeviceDetails': [Record<string, any>]
  'change-devices': []
  'device-created': []
  'device-detail-saved': []
}>()

內部結構:使用 v-model 雙向綁定既有 SystemPicker / DevicePicker 的所有 props,emit 透傳父層。元件本身不做 API 呼叫、不存狀態,純粹是 layout wrapper。

不顯示:「未來支援層級關聯」hint(user 確認不要)。

後續調整(v0.2):實作後 user 試用發現「合併 tab 沒有 Save 按鈕」與其他有 Save 的 tab 並列產生違和感(沿用原本即時 sync 設計)。後加上:

  • Settings 模式下加 ⓘ 小字 hint「本區塊變更會即時自動儲存」(i18n key section_audit_scope_autosave_hint
  • syncProjectSystems / syncProjectDevices 成功後 toast「已自動儲存」(life 1500ms,i18n key toast_autosaved
  • Create 模式(wizard)不顯示此 hint,因為 wizard 是 deferred save 不是即時 sync

6. i18n 變更

6.1 新增 key(zh-tw + en 同步)

key zh-tw en 用途
project_settings.tab_audit_scope 受評範圍 ({systems} 系統 / {devices} 設備) Audit Scope ({systems} systems / {devices} devices) Settings tab header
project_settings.section_audit_scope_desc 本專案要稽核的資訊系統與設備 Information systems and devices in scope for this audit Settings 模式下區塊描述
project_settings.section_audit_scope_autosave_hint 本區塊變更會即時自動儲存,無需按存檔 Changes in this section are saved automatically — no save button needed Settings 模式下 ⓘ 小字提示(v0.2 加)
project_settings.toast_autosaved 已自動儲存 Auto-saved sync 成功後 toast(v0.2 加)
project_create.step_audit_scope 受評範圍 Audit Scope Create wizard step header
project_create.audit_scope_title 受評範圍 Audit Scope Create step 內標題
project_create.audit_scope_subtitle 選擇本專案要稽核的系統與設備 Select systems and devices for this audit Create step 內副標題
project_create.review_audit_scope 受評範圍 Audit Scope Confirm 頁區段標題
project_create.review_audit_scope_systems 資訊系統 Information Systems Confirm 頁子區塊
project_create.review_audit_scope_devices 設備 Devices Confirm 頁子區塊

註:project_settings.section_audit_scope(純標題、不含 desc)原本也有定義但未被消費,留待 cleanup PR 移除。

6.2 保留 key

section_audit_systems / section_devices / tab_audit_systems / tab_devices 仍由內部 SystemPicker / DevicePicker 使用,保留不刪。PR review 時再決定要不要清理舊的 tab key。

6.3 檔案路徑

依專案既有 i18n 切分慣例(待 implementer 在 implementation 階段確認實際 file 結構):

  • src/config/locales/i18n/zh-tw/project-settings.json(或對應檔)
  • src/config/locales/i18n/en/project-settings.json

7. 後端不變說明

PUT /api/1.0/grc-project/<id> 已支援以下三種 request body:

// 只更新系統
{ "audit_systems": [{"information_system_uid": "..."}, ...] }

// 只更新設備
{ "devices": [{"device_uid": "..."}, ...] }

// 同時更新(也支援,但 Phase 1 不使用此模式)
{ "audit_systems": [...], "devices": [...] }

Phase 1 維持目前 FE 的「即時 sync 各自呼叫」模式:使用者勾選系統 → 立刻 PUT audit_systems;勾選設備 → 立刻 PUT devices。不做合併呼叫。

8. 實作步驟(建議順序)

  1. AuditScopeSection.vue:先獨立元件,定義 props/emits 介面
  2. i18n 加 key:兩語系同步加(zh-tw + en),先不刪舊 key
  3. ProjectSettingsView.vue:tab 合併(4 → 3),用 AuditScopeSection 取代原 tab 2 + tab 4
  4. ProjectCreateView.vue:step 重排(5 → 4,順序 (2)),確認頁區段更新
  5. i18n key audit:grep 確認沒有 dead key
  6. 建立 changelogdocs/changelog/2026-04-25-audit-scope-merge.md

9. 測試計畫

9.1 ProjectSettings 手測

9.2 ProjectCreate 手測

9.3 Regression(重點顧及,必跑

9.4 i18n 測試

10. 風險評估

風險 機率 影響 mitigation
i18n key rename / 漏翻 低(顯示 raw key) 兩語系同步加 + grep audit
user 找不到原本的 tab 低(位置變但功能在) tab header 計數明確顯示「N 系統 / M 設備」
確認頁區段重排造成 user 慣性錯亂 極低 只是視覺合併,資料不變
AuditScopeSection 透傳 props 漏接 中(功能殘缺) 開發階段對照 SystemPicker / DevicePicker 完整 props 列表
Step 順序調整造成既有教學文件 / 截圖過時 改完 PR 描述列出文件需更新清單

11. 變更紀錄要求

依 CLAUDE.md 規範,PR 落地時必須建立:

  • docs/changelog/2026-04-25-audit-scope-merge.md

內容包含:需求說明 / 變更檔案清單 / 行為差異(無 API 變更)/ 參考連結到本 design doc。

12. 後續引用

  • 實作計畫文件(之後寫):docs/features/FR-017-2604-audit-scope-merge/implementation-plan.md
  • 變更紀錄:docs/changelog/2026-04-25-audit-scope-merge.md(PR 落地時建立)