---
title: "跨框架控制項對應 — 設計文件 (FR-162)"
brand: "Guidant AI · **FR-162** 跨框架控制項對應"
eyebrow: "FR-162 · Cross-framework Control Mapping — 設計文件 · 2026-10-24"
h1: "跨框架控制項對應"
subtitle: "框架之間的要求對得起來、顧問能調、證據能跨專案沿用；對照存成獨立的 OSCAL Mapping Collection，不塞進任一本 catalog"
lede: "客戶做完 CMMC 再做 ISO 27001 時，兩套框架有一半的要求其實是同一件事，但現在是兩個獨立專案、證據要重傳一遍。本案建一份**跨框架對照庫**（控制項層＋評估目標層），第一批灌 NIST 官方公布的 CMMC↔800-53↔ISO 鏈；資源庫有一個**對照維護工作台**讓顧問調整、匯入匯出；新專案規劃頁有「**沿用其他專案的證據**」一鍵批次：系統配對全專案評估目標、預覽後一次沿用，沒對應的順便列成差異清單；任務層保留單筆沿用當補救；三個時點主動提醒 user「有符合的證據可以用了」；官方只到控制項層的部分，由 AI 在小範圍內建議評估目標配對、顧問確認。本文件寫定案規則、為什麼這樣定、踩到會怎樣。"
chips: [
  {text: "設計定案（D1～D8）；對照表進 jedi-oscal-v2，決策者已同意", kind: ok},
  {text: "對齊 OSCAL 1.2.2 mapping-collection", kind: accent},
  {text: "雛形：prototype/index.html（三頁）", kind: accent},
  {text: "不做一專案多框架", kind: plain}
]
footer: "FR-162 · 跨框架控制項對應 — 設計文件 · 2026-10-24 · 分析 docs/analysis/2026-10-24-cross-framework-control-mapping.md · 前作：FR-038 OSCAL v2、FR-053.1 覆核輪承襲、FR-139 資源庫"
---

| 項目 | 內容 |
|---|---|
| 狀態 | 已實作（母卡 CM-3057，子卡 25 張皆驗收通過待確認或跳過）；第 3 棒依決策者實測直改的規則見下方「控制項關聯管理：現行規則」 |
| 日期 | 2026-10-24 |
| 分析 | [docs/analysis/2026-10-24-cross-framework-control-mapping.md](../../analysis/2026-10-24-cross-framework-control-mapping.md)（現況盤點、官方對照資料盤點、被排除方案） |
| 雛形 | [prototype/index.html](prototype/index.html)（五頁：框架對照工作台、點一顆對應後的對話框、新專案一鍵沿用（舊）、沿用其他專案的證據獨立頁（2026-10-25 定案）、任務層單筆沿用與標籤；雙擊即可開） |
| 前作 | FR-038（OSCAL v2 四層架構、`source_identifier`）、FR-053.1（覆核輪證據承襲）、FR-139／FR-161（資源庫頁與程序書池） |
| 官方資料 | 決策者桌面 `00_Compliance Manager/07_稽核法規文件/Mapping/`（OLIR 800-53↔ISO 2022、CSF↔800-53、ENISA CRA）；`NIST 800-171/r2/NIST.SP.800-171r2.pdf` 附錄 D；主專案 `common/util/input/NIST_800-53_to_800-171.xlsx` |

## 需求背景與端到端流程 {#why nav="背景"}

客戶導入合規框架常不只一套：先 CMMC、再 ISO 27001，或加 CRA。系統裡每套框架是一個獨立專案，第二套的證據要重新上傳一遍，user 覺得在重工。NIST 有公布 800-53↔ISO 27001 的對照、800-171 附錄 D 有 800-171↔800-53 的對照、CMMC 的 practice 編號本身就是 800-171 編號；這些知識現在系統裡沒有落點。

本案要做到：系統知道「框架 A 的這條要求就是框架 B 的那條」，細到**評估目標（AO，assessment objective，每條控制項底下要逐一判定的小項）**；user 做第二套框架時，收證據的畫面直接列出「你在 CMMC 專案對應的評估目標已交過這幾份，要不要沿用」。對照知識有三個來源：官方公布、我們用 AI 分析補、顧問討論後手動調，所以要有工作台能調、能匯入匯出。

```{.mermaid cap="圖 1 — 對照怎麼進來、怎麼被用"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
  subgraph 來源
    O[官方 Excel<br/>OLIR／附錄 D]
    J[OSCAL mapping-collection JSON]
    AI[AI 建議 AO 配對<br/>背景工作]
    C[顧問在工作台調整]
  end
  subgraph 對照庫["對照庫（jedi-oscal-v2，資源庫層跨專案共用）"]
    M[control_mappings<br/>一對框架版本一筆]
    E[control_mapping_entries<br/>控制項層與 AO 層對應邊<br/>官方層／覆寫層分開]
  end
  subgraph 使用
    W[資源庫 對照維護工作台<br/>看、改、匯入匯出]
    T[任務收證據<br/>「沿用其他框架的證據」]
    R[報告引用對照版本]
  end
  O --> M; J --> M; AI --> E; C --> E
  M --> E
  E --> W; E --> T; E --> R
  T -->|勾選沿用| EV[job_evidences 新一筆<br/>同檔、帶來源標記]
```

```{.mermaid cap="圖 2 — 新專案一鍵沿用（主要入口）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
  participant U as user（新 ISO 專案，規劃階段）
  participant BE as BE 證據沿用服務
  participant MP as 對照解析（套件）
  participant EV as 其他專案的 job_evidences
  BE-->>U: 規劃頁橫幅：「CMMC L2 專案『鼎盛精密』有 87 個評估目標的證據可沿用」
  U->>BE: 點「查看並沿用」，選來源專案（可多選）
  BE->>MP: 本專案每個 AO 代號 → 對應到來源框架的哪些 AO／控制項？
  MP-->>BE: 每個 AO：目標代號、relationship、confidence、origin、hops
  BE->>EV: 同租戶來源專案中綁到那些代號的任務 → 有效證據
  EV-->>BE: 證據清單
  BE-->>U: 預覽表（每列：本專案 AO／來源／關係／信心／證據數／預設勾選）＋「31 個 AO 無對應」
  U->>BE: 調整勾選後按「沿用所選」
  BE->>EV: 每份證據新開一筆 job_evidences：同 file_id、source=REUSED、reused_from_evidence_id、mapping_entry_id
  BE-->>U: 摘要：沿用 142 份到 87 個 AO；專案記 last_evidence_reuse_at
```

## 分工概述（30 秒版） {#overview nav="分工概述"}

**整案一句話**：建一份跨框架對照庫並讓顧問可維護，收證據時能把其他框架專案已交的證據帶進來，不再重傳。

| 批 | 卡 | 做什麼 | 產出 | 完成怎麼判定（決策者檢查法） |
|---|---|---|---|---|
| 1 | 1a 套件對照表與匯入匯出 | 兩張表、解析服務、OSCAL JSON 與 Excel 匯入匯出、CMMC↔800-171 規則生成 | 套件 migration 021／022、服務、套件測試 | 套件測試綠；`jedi_oscal_v2/migrations/021、022` 套進 DEV 兩表存在 |
| 1 | 5 OSCAL 版本標記修正 | 套件 catalog 預設版本改 1.2.2 ＋ 回填 | 套件一行＋ migration 023 | DEV `oscal.metadata.oscal_version` 全部 1.2.2 |
| 2 | 1b BE 接線與灌官方資料 | DI wiring、三份官方檔轉接腳本、DEV 灌資料 | `di_containers` 一處、`scripts/import_official_control_mappings.py`、DEV 有資料 | 一句 SQL 看對照筆數（CMMC↔171 AO 層、171↔53、53↔ISO）；匯出一份 JSON 打開有 `mapping-collection`；重跑灌資料筆數不變 |
| 3 | 2a 工作台 BE | route、能力點、選單 migration、矩陣端點（主軸×欄一次回含路徑）、寬表匯入匯出、error code | BE 端點可打 | Swagger 打 matrix（axis=ISO, cols=CMMC,CRA,SEMI）回整張；wide export 再 import 筆數不變 |
| 4 | 3a 證據沿用 BE | 共用讀模型、五支 API、`job_evidences`／`projects` migration | BE 端點可打 | 對 DEV 既有 CMMC 專案與一個 ISO 專案打 preview 回得到配對；打 reuse 後 DB 多 `source='REUSED'` 筆數＝摘要 |
| 5 | 2b 工作台 FE | 矩陣視圖：選主軸、勾並排欄，每格膠囊；點膠囊右側面板看路徑／覆寫／停用／歷史；寬表匯入匯出；AI 建議按欄 | 頁面 | 主軸 ISO 並排 CMMC／CRA／SEMI 看到矩陣與空格；點一顆看到三跳路徑；覆寫一條→DB 新 curated 列、官方列 disabled；匯出寬表再匯入一致 |
| 6 | 3b 證據沿用 FE（專案層） | 規劃頁橫幅提醒＋三步 dialog（選來源→預覽→摘要） | 元件 | 新 ISO 專案進規劃頁看到橫幅；選 CMMC 專案，預覽表每列有對應與證據數；沿用後摘要寫「N 份到 M 個 AO、K 個無對應」 |
| 7 | 3c 證據沿用 FE（任務層） | drawer 提示列＋單筆沿用、沿用 Tag、AR 頁 Tag | 元件 | 任一任務 drawer 看到提示列與沿用證據的「沿用自 CMMC 專案」標籤；AR 判定頁同標籤 |
| 7 | 4 AI 建議配對 | 背景工作兩模式：B 控制項層初配（CRA／SEMI 對 ISO／CMMC 的起點）、A 評估目標層配對；A 跑完規則回填控制項層關係；工作台下拉兩項與「確認」 | handler＋工作台兩處 | 工作台對 CRA↔ISO 按「初配控制項」，幾分鐘後控制項層出現 ai-suggested 列；確認幾條後按「配對評估目標」，AO 層出現列且控制項層關係被回填；抽三條人工看合不合理 |

**依賴一句**：1b 等 1a；2a、3a 都等 1b 但**不能同批**（兩張都改 `flow_control_error_code.py` 與 `manifest.tsv` 這類登記型檔）；2b／3b／3c 同理不能同批（都改 `api.js`、locales、`error-code.json`）；卡 4 等 2b；3c 與 4 可同批（不同 repo 不同檔）。卡 5 與 1a 同批（套件不同檔）。**決策者功課**：驗收卡 1 前把 `Mapping/` 三份檔案留在桌面原位，runner 要讀。

## 決策定案表 {#decisions nav="決策"}

| # | 決策 | 定案 | 理由與被排除方案 |
|---|---|---|---|
| D1 | 對照存哪 | **獨立 mapping 表，放 `jedi-oscal-v2`**（決策者 2026-10-24 裁「這應該是套件功能」） | OSCAL Control Layer 三個平行 model：Catalog／Profile／Mapping Collection，對照本來就不在 catalog 裡。排除「塞 `catalog_controls.links`」：單向、無關係型別、無信心度，框架改版等於重寫 catalog；與 2026-10-10 CRA 裁示衝突。排除「復活 v1 `oscal_control_mapping`」：只有邊、無關係型別／AO 層／來源。 |
| D2 | 鍵用什麼 | **控制項代號 `control_id`（`A.5.1`／`AC.L2-3.1.1`）＋ AO `part_id`（`A.5.1_obj.3`）**，不綁數字 id | 資源庫 clone、專案 clone 會長新 id（DEV 14 個 catalog 只有 5 個母版），綁 id 每 clone 一次要複製對照。綁代號則母版與副本共用一份。官方長寫法（`ISO/IEC 27002:2022 5.1`）存 remarks 供報告引用。 |
| D3 | 粒度 | **雙粒度**：同一張表容納控制項層與 AO 層；解析時 AO 層有就用，沒有退控制項層並降一級信心度 | 官方全是控制項層；AO 層由 CMMC↔800-171 規則生成、AI 建議、顧問補。排除「只做控制項層」：ISO 一條 2～3 個 AO、CMMC 3～6 個，推導出的候選雜訊多一倍。排除「只做 AO 層」：無官方背書、715 個 AO 全人工。 |
| D4 | 證據怎麼重用 | **跨專案建議沿用**，不做一專案多框架（決策者裁 OK） | 一專案多框架要動 resolver、SSP 匯出、AP 草稿、AR 判定、報告包全部，且 ISO 有 SoA、CMMC 沒有。沿用時**新開一筆 `job_evidences`**同 `file_id`（檔案本體一份、不複製；證據紀錄各專案一筆），比照覆核輪承襲（`inherited_from_round_id`）形狀；對 user 是「關聯」——來源換新版不自動跟（已凍結的稽核證據不可變動），要跟就再帶一次。排除「共用同一筆」：凍結／刪除／覆核承襲語意打結。 |
| D5 | 官方與自家改動怎麼共存 | **官方層與覆寫層分開**：`origin` 區分 official／third-party／curated／ai-suggested（third-party＝OSA、SunSquare 這類二級來源灌入，與 official 一樣是「既有可信起點」，不覆蓋），顧問改官方條時不改原列，另存 curated 列並記 `overrides_entry_id`；解析時覆寫層優先 | 官方更新重匯時不沖掉顧問調過的；畫面看得出哪些是自己改的。 |
| D6 | 匯入語意 | **更新不覆蓋**：同鍵覆蓋、新鍵新增、檔內沒有的不刪；要回到原廠資料用「還原出廠設定」（主軸 × 一欄，二次確認） | 比照框架 Excel 匯入既有庫只增不刪。「以檔案取代整欄」不提供：跟匯入重複、又會硬刪紀錄。 |
| D7 | 800-53 這種我們沒有 catalog 的中繼框架 | **允許對照的來源／目標指向「只有代號沒有 catalog」的框架版本**：在 `oscal.frameworks`／`framework_versions` 建 NIST 800-53 R5 與 800-171 r2 兩筆，`catalog_id` 留 NULL（欄位本來可空） | 800-53 是所有官方對照的樞紐。排除另開「參考框架表」：既有表欄位夠用，多一張表多一套 join。畫面上 `catalog_id` NULL 的框架標「參考用，無目錄」。 |
| D9 | 沿用的入口與形狀 | **專案總覽「證據作業」下拉 → 獨立頁；一個來源專案一份沿用設定，隨時調整，不做批次**（決策者 2026-10-25 改判，取代「規劃頁三步對話框」） | 入口：專案總覽一顆「證據作業」下拉，收「上傳檔案，AI 自動分類」「從雲端資料夾分類」「沿用其他專案的證據」三項（同屬收證據動作，放一起才找得到）；規劃頁的常駐按鈕與橫幅移除。頁面比照自動分類：**首頁＝來源專案列表**（一列一個來源：目前沿用檔數→評估目標數、被判定引用數、最後調整、ⓘ 調整紀錄彈窗、「調整」「全部取消」）；「選擇來源專案」只列尚未選過的；**調整頁＝三欄可拖**（來源框架導航／該節點證據檔／歸到本專案哪些控制項與評估目標），右欄複用自動分類的 `PlacementTable`＋`AOPickerDialog`，預設勾「通用」（評估目標層與控制項層一視同仁：控制項層對應＝來源控制項全部評估目標都適用；已細分的列不算）、「部分可用」在候選區不勾、可加任何一條；右欄依控制項分組；中欄檔案可預覽；「一鍵勾選通用對應（N）」一次勾好再微調，N＝0 時停用並說明原因；「套用變更（＋N／－M）」一次存。**全部取消**＝該來源帶進來的全拿掉，被稽核判定引用的擋下不刪並列出。**每次調整留唯讀紀錄**（誰、何時、＋N／－M、擋下幾筆），走既有稽核事件機制，不可復原某一次。決策者原話：「同一個專案不要重複沿用，就是只能取消全部，不要做這麼複雜，就是一直去那個裡面調整對應結果就好」「我們要有記錄就好」。雛形 `prototype/project-reuse-page.html`。**命名**（2026-10-24 定）：畫面「證據沿用」，程式與 DB 一律 `reuse`；覆核輪仍叫「承襲」。沿用的是證據檔不是判定結果。 |
| D10 | 怎麼提醒 user | **兩個提醒點**：① 證據作業下拉旁一行小字「偵測到〈專案〉有 N 個評估目標的證據可沿用」（含來源新增證據時的「還有 N 個新證據」）；② 任務 drawer 證據區提示列＋常駐「沿用」按鈕 | 原規劃頁橫幅（2026-10-24 D10①③）隨入口搬家併成總覽頁一行小字，不再做可關閉橫幅；沿用列表首頁上方也顯示同一句並附「去調整」。drawer 單筆沿用維持。 |
| D11 | 「其他專案」的範圍 | **當前使用者看得到的專案**（與專案列表同一套可見性：owner 或參與者，admin 全部），再排除封存、已刪除、本專案（決策者 2026-10-25 改判，取代「第一版＝同租戶」） | 2026-10-25 實查：DEV 租戶 102 的鼎盛 CMMC L2 專案屬另一組織單位（176），使用者專案列表只有 3 個，沿用來源卻列出它且能拉它的證據——同租戶範圍是**跨組織單位資料外洩**；也解釋 CM-3072 那次 3,599 筆爆量混進鼎盛 204 份檔。RLS 只擋租戶，可見性在應用層：canonical 是 `infra/readmodel/audit/flow_control_dashboard_repo_impl.py:_visible_project_ids`（owner／`_related_project_ids_subq`／admin），沿用來源改用它。封存（`status='archived'`）原本也沒排除。資訊系統過濾仍待 ISO 資產設計。 |
| D8 | 評估目標層與無官方對照的框架怎麼產 | **AI 兩種模式＋規則回填**：模式 A（AO 層）對控制項層已對上的組，兩邊 AO prose 兩兩比；模式 B（控制項層初配）給一對框架版本，AI 讀控制項標題＋敘述做初配，候選用提示對（ENISA／第三方對照、顧問勾選）或先選 group 兩階段縮小；模式 A 跑完用明確規則回填控制項層關係型別（AO 全等價→等價、全不相關→不相關、其餘部分重疊）。CMMC↔800-171 r2 仍規則生成。顧問在工作台確認後才算正式 | 決策者 2026-10-24 指出：官方資料只讓 CMMC／800-171／ISO 對得起來，CRA 與 SEMI 連控制項層都空，只有模式 A 時它們沒起點，第一版進不了沿用流程；且官方全不給關係型別，D9 的預設勾選對官方鏈幾乎全空。重查後 CRA 有 OSA `cra.json`（→800-53，CC BY-SA）、SEMI 有 SunSquare 表 2（→IEC 62443）兩份二級來源可灌，初配只補 OSA 未覆蓋的 CRA Article 13／Annex VII 與 62443-4-1 五個 practice（見 `docs/analysis/2026-10-24-mapping-sources-for-frameworks-without-official-crosswalk.md`，含來源三級分級）。不做 AO 全配（六萬對）。AI 準確率未實測，報告輸出必標來源；AO 的 `_obj.N` 是我們依匯入順序編的非官方編號，mapping 綁框架版本、改版即新對照。 |
| D12 | 對照的層級 | **只能同層**：控制項對控制項、評估目標對評估目標（決策者 2026-10-25 裁，10-26 補實作規則）。後端 `add_entry` 一處把關：主軸評估目標寫到目標控制項的直連自動展開成逐條評估目標，其餘跨層 400 `GRC_400189`。解析器補一條：排除了某控制項的任一評估目標，同一控制項的控制項層命中也不再成立 | 矩陣、證據沿用、Excel 共用同一套解析，開「評估目標→控制項」例外，每個讀取端都要各自認。少了解析器那條，逐條標評估目標不可用擋不住多跳退回的控制項層，只能寫跨層列。決策推理見 `docs/analysis/2026-10-26-fr162-same-level-mapping-and-evidence-delete-semantics.md` |
| D13 | 刪除沿用來的證據 | **只動本專案**：同專案多處掛同一份檔可選「只這一處／本專案全部」；沿用來的那筆刪了寫 `deleted_reason='NOT_APPLICABLE'`＝本專案此處不適用，沿用建議不再帶回、調整頁可勾回；來源刪除下游照常可用，標「來源已移除」；刪掉的證據本專案不再給下載（決策者 2026-10-26 裁） | 排除「連帶刪除其他專案」：跨過權限與輪次狀態，原上傳者無權替其他專案決定。排除「用 `USER_DELETED` 判不適用」：調整頁拿掉與歷史清理共用這個值，整輪建議會消失。排除「沿用或刪除時複製檔案」：檔案不可改、軟刪不刪檔、外鍵保護，複製只多儲存空間。`job_evidences` 不開 RLS（背景與系統脈絡路徑太多）。推理同上份分析 |

## 現況接入點盤點 {#touch nav="接入點"}

| 元件 | 現況 | 本案動作 |
|---|---|---|
| `jedi_oscal_v2` catalog 層 | `catalog_controls.source_identifier` 是同框架跨版本代號；`catalog_control_parts.name='assessment-objective'`、`part_id` 格式 `<control_id>_obj.N` | 不改。對照表鍵對 `control_id` 與 `part_id` |
| `jedi_oscal_v2` profile 層 | `ProfileResolutionService.resolve` 已迭代多筆 import | 不改 |
| `jedi_oscal_v2/app/service/io/oscal_io_service.py` | 匯入匯出 catalog／profile／SSP／AP／AR／POA&M，無 mapping | 加 mapping-collection 匯入匯出（卡 1） |
| `jedi_oscal_v2/migrations/` | 到 020 | 加 021（兩表）、022（800-53／800-171 參考框架版本 seed）、023（oscal_version 回填，卡 5） |
| `jedi_oscal_v2/app/service/catalog/catalog_service.py:90` | 預設 `oscal_version="1.1.2"`，DEV 53 筆錯標 | 改 1.2.2（卡 5） |
| 主專案 `di_containers/oscal/oscal_containers.py` | wiring 套件服務（`providers.Factory`） | 加 mapping 相關 service（卡 1） |
| 主專案 `api/oscal/routes/resource_library_route.py`、能力點 `oscal.resource-library.*` | 四顆 CRUD | 加 `oscal.control-mapping.read`／`manage`（卡 2）；寫入另過 `require_tenant_hq`（對照全站共用，只開放總部改） |
| `public.ui_routes` | 側選單走 migration 登記（FR-161 先例 `2026-10-23-fr161-pre-engagement-capabilities.sql`） | 加「控制項關聯管理」入口，掛在「合規稽核」群組（卡 2） |
| `compliance.job_evidences` | `source` CHECK 四值；`inherited_from_round_id` 承襲標記 | `source` 加 `REUSED`；加 `reused_from_evidence_id`、`mapping_entry_id`（卡 3，主線 migration） |
| `public.workflow_execution_control_mapping` | 任務 ↔ `control_id`＋`ao_part_id`＋`round_id`，證據→AO 的權威鏈 | 不改，卡 3 讀它找「對應 AO 的任務」 |
| `infra/readmodel/audit/ar_evidence_candidate_query.py` | 既有「某控制候選證據池」讀模型 | 卡 3 新讀模型照它的 scope 紀律（二次用 wecm scope） |
| FE `src/components/grc/JobExecutionDrawer.vue`（2,360 行，超標） | 證據區塊在 `job-drawer/JobEvidenceLinksSection.vue`，承襲標籤 `inherited_from_prev_round` | 新區塊**新開 `job-drawer/JobEvidenceReuseSection.vue`**，不往 drawer 塞（卡 3） |
| FE `src/views/module_frame/`、`src/config/router/index.js`、`src/config/capabilities.js` | 資源庫列表頁與範本編輯頁 | 新頁 `src/views/control-mapping/`（卡 2） |
| 背景工作 `app/background_job/handler_registry.py`、AI 閘道 `core/plugins/_source_scan_ai.py` 範式 | `@register_job_handler`；`gateway.complete(req)` 經 `select_model_for_current_tenant` | 卡 4 照抄 |

## 詳細設計 {#detail nav="詳細設計"}

### 資料表（套件 `jedi_oscal_v2/migrations/021-control-mappings.sql`）

```sql
-- 一對框架版本一筆（對齊 OSCAL mapping）
CREATE TABLE oscal.control_mappings (
  id                          BIGSERIAL PRIMARY KEY,
  uuid                        UUID NOT NULL UNIQUE,
  source_framework_version_id BIGINT NOT NULL REFERENCES oscal.framework_versions(id),
  target_framework_version_id BIGINT NOT NULL REFERENCES oscal.framework_versions(id),
  title                       TEXT NOT NULL,
  method                      VARCHAR(64),            -- OSCAL provenance.method（manual / tool-assisted / rule）
  matching_rationale          VARCHAR(32),            -- syntactic / semantic / functional（IR 8477）
  status                      VARCHAR(20) NOT NULL DEFAULT 'draft',   -- draft / published / deprecated
  provenance                  JSONB,                  -- 來源文件、頁次、網址、匯入檔名、匯入時間
  remarks                     TEXT,
  created_at / updated_at / created_user / updated_user,
  UNIQUE (source_framework_version_id, target_framework_version_id)
);

-- 一筆對應邊（對齊 OSCAL map）
CREATE TABLE oscal.control_mapping_entries (
  id                   BIGSERIAL PRIMARY KEY,
  uuid                 UUID NOT NULL UNIQUE,
  mapping_id           BIGINT NOT NULL REFERENCES oscal.control_mappings(id) ON DELETE CASCADE,
  source_level         VARCHAR(10) NOT NULL CHECK (source_level IN ('control','objective')),
  source_control_code  VARCHAR(128) NOT NULL,          -- = catalog_controls.control_id
  source_part_id       VARCHAR(255),                   -- objective 時 = catalog_control_parts.part_id
  target_level         VARCHAR(10) NOT NULL CHECK (target_level IN ('control','objective')),
  target_control_code  VARCHAR(128) NOT NULL,
  target_part_id       VARCHAR(255),
  relationship         VARCHAR(20) NOT NULL CHECK (relationship IN
                         ('equivalent-to','subset-of','superset-of','intersects-with','not-related-to')),
  confidence           NUMERIC(3,2) CHECK (confidence BETWEEN 0 AND 1),
  origin               VARCHAR(16) NOT NULL CHECK (origin IN ('official','third-party','curated','ai-suggested')),  -- third-party 由套件 024 補
  status               VARCHAR(16) NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled')),
  overrides_entry_id   BIGINT REFERENCES oscal.control_mapping_entries(id),  -- curated 蓋 official 時指回原列
  remarks              TEXT,
  created_at / updated_at / created_user / updated_user,
  UNIQUE (mapping_id, source_control_code, source_part_id, target_control_code, target_part_id, origin)
);
CREATE INDEX ON oscal.control_mapping_entries (mapping_id, source_control_code);
CREATE INDEX ON oscal.control_mapping_entries (mapping_id, target_control_code);
-- GRANT ... TO cm_app ＋ sequence 權限；兩表非 tenant-scoped（對照是產品知識，跨租戶共用），不掛 RLS
```

變更紀錄不另開表：`control_mapping_entries` 只增不改，顧問改一條＝原列 `status='disabled'` ＋ 新列 `overrides_entry_id` 指回；誰改、何時在新列的 `created_user`／`created_at`。工作台「歷史」就是沿 `overrides_entry_id` 往回走。

### 參考框架（`022-reference-framework-versions-seed.sql`）

在 `oscal.frameworks` 建 `NIST_SP_800_53_R5`（authority NIST）、`NIST_SP_800_171_R2`（authority NIST），各一個 `framework_versions`，`catalog_id` NULL。`ON CONFLICT (uid) DO NOTHING`，uid 寫死在 migration。

### 各框架怎麼轉到 ISO 27001：轉乘路線

五個產品框架之間**沒有任何一組直接對照**，全部經參考框架中轉；NIST SP 800-53 R5 是唯一通往 ISO 的匝道（ISO 只與 800-53 有官方對照，即 NIST OLIR）。參考框架只有代號沒有目錄，矩陣預設隱藏，解析結果一律帶 `hops` 與每跳 `origin`，顧問看得出一條對應是怎麼來的。

```{.mermaid cap="圖 3 — 轉乘路線：實線＝官方（official），虛線＝二級來源（third-party）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB'}}}%%
flowchart LR
  subgraph P["產品框架（有 catalog）"]
    L1[CMMC 2.13 L1]
    L2[CMMC 2.13 L2]
    CRA[CRA 2024/2847]
    SEMI[SEMI E187]
  end
  subgraph R["參考框架（只有代號，無 catalog）"]
    N171[NIST SP 800-171 r2]
    N53[NIST SP 800-53 R5]
    I62[IEC 62443]
  end
  ISO[ISO/IEC 27001:2022]
  L1 -->|"恆等規則<br/>控制項＋AO 層"| N171
  L2 -->|"恆等規則<br/>控制項＋AO 層"| N171
  N171 -->|"800-171 r2 附錄 D"| N53
  N53 -->|"NIST OLIR 2023-10-12"| ISO
  CRA -.->|"OSA cra.json"| N53
  SEMI -.->|"SunSquare 表 2"| I62
  I62 -.->|"800-82 r3 附錄（官方）<br/>或 OSA（社群）"| N53
```

| 起點 | 到 ISO 幾跳 | 經過 | 每跳來源 | 備註 |
|---|---|---|---|---|
| CMMC L1／L2 | 3 | 800-171 → 800-53 | 恆等規則 → 附錄 D → OLIR，全官方 | 唯一有 AO 層的一跳在 CMMC↔800-171，過了 800-53 退回控制項層 |
| CRA | 2 | 800-53 | OSA（二級）→ OLIR（官方） | OSA 不涵蓋 Article 13 與 Annex VII 共 17 條，交 AI 初配 |
| SEMI E187 | 3 | 62443 → 800-53 | SunSquare（二級）→ 800-82 r3 附錄（官方，待取得）或 OSA（二級）→ OLIR（官方） | 至少一跳二級，信心最低；三個來源都不含 62443-4-1 五個 practice，交 AI 初配 |
| CMMC ↔ CRA | 3 | 800-171 → 800-53 | 兩邊各自匯到 800-53 再接 | 不經 ISO |

**每一跳的資料從哪來、有多可信**（決策者 2026-10-24 追問「資料怎麼來的、什麼網站、什麼 PDF、有公信力嗎」）：

| 跳 | 來源名稱 | 誰發的 | 檔案／網址 | 粒度 | 公信力 | 灌入時標 |
|---|---|---|---|---|---|---|
| CMMC → 800-171 r2 | CMMC 2.13 模型本身 | 美國國防部（DoD CIO） | 《CMMC Model Overview v2.13》《Assessment Guide L2 v2.13》，dodcio.defense.gov/CMMC；桌面 `07_稽核法規文件/CMMC 2.0/` | 控制項＋AO（practice 編號＝800-171 編號、AO＝800-171A 判定句） | **最高**：不是對照表，是定義本身 | `official`，規則生成 |
| 800-171 r2 → 800-53 R5 | 800-171 r2 附錄 D | NIST | 《NIST.SP.800-171r2.pdf》附錄 D 表 D-1～D-14；桌面 `NIST 800-171/r2/`；表格版 BE `common/util/input/NIST_800-53_to_800-171.xlsx` | 控制項 | **高**：NIST 自發，但 NIST 自註「informational」、ISO 欄是 2013 版不用 | `official` |
| 800-53 R5 → ISO 27001:2022 | NIST OLIR Informative Reference #155 | NIST（National Online Informative References 計畫） | `sp800-53r5-to-iso-27001-mapping-2022-OLIR-2023-10-12-UPDATED.xlsx`；入口 csrc.nist.gov/pubs/sp/800/53/r5/upd1/final「Supplemental Material」；桌面 `Mapping/` | 控制項（含 enhancement） | **高**：NIST 自發、有 IR 8477 方法論與 OLIR 審查流程；但不給關係型別（Strength 欄全空）。**ISO 自己沒發對 NIST 的對照** | `official` |
| CRA → 800-53 R5 | OpenSecurityArchitecture `osa-data` | **社群**：OSA 是 2008 年起的開源資安架構志工專案（opensecurityarchitecture.org），GitHub 組織 `opensecurityarchitecture/osa-data`；無署名作者、無標準組織審核 | 官網 https://www.opensecurityarchitecture.org ；資料庫 https://github.com/opensecurityarchitecture/osa-data ，檔 `data/framework-coverage/cra.json`（CC BY-SA 4.0）；桌面 `Mapping/osa-cra-to-sp800-53r5.json`；repo `scripts/data/osa_cra_to_sp800-53r5.json`（與桌面逐 byte 相同） | 條→控制項清單，附 coverage % 與 rationale | **中偏低**：每條有理由可逐條核，但 coverage 是估的、頁面自註「no line-by-line verification recorded」；稽核報告寫「依 OSA」會被問「OSA 是誰」。**只當顧問起點，確認後才算** | `third-party` |
| CRA 官方對照（不灌） | ENISA《CRA implementation via EUCC》附錄 | ENISA（歐盟網路安全局） | `ENISA-CRA-implementation-via-EUCC-annex-2025-02.pdf`（93 頁，2025-02）；桌面 `Mapping/` | Annex I 每條 → Common Criteria SFR | **高**，但對的是 ISO/IEC 15408 不是 ISO 27001／800-53，我們無 CC catalog | 不灌，留證 |
| CRA 正式對照（未出） | CEN/CENELEC prEN 40000-1-4 | 歐洲標準組織（受 EC 標準化請求 M/606） | cencenelec.eu；預計 2027-10 | Annex I Part I 2(a)～(m) 技術要求目錄 | 出來後是**唯一正式**依據 | 出來即換 |
| SEMI E187 → IEC 62443 | SunSquare Tech 文章表 2 | **第三方個人**：SZ Lin（林上智），SunSquare Tech；同一作者在 SEMI 官網掛過《ISA/IEC 62443 Status Updates》簡報，有 SEMI 資安工作組背景，但文章與簡報都非 SEMI 正式出版 | https://sunsquare.tech/2026/07/08/understanding-the-relationship-between-semi-e187-and-isa-iec-62443-in-equipment-security-implementation/ （表格是圖片）；桌面 `Mapping/SunSquare-image*.webp`；抄錄在 analysis §四 | E187 12 條 → 62443-4-1 practice／3-3 SR／4-2 CR | **中偏低**：作者明標「author's interpretation … not an official cross-standard specification」；對應邏輯與 METI 指引、E187 條文一致。**SMCC WG4 官方比較分析出來即換** | `third-party` |
| SEMI 其他（提示用） | METI《半導體工廠 OT 安全指引》v1.0 | 日本經濟產業省 | `semiconductor_systems_guideline_ver1.0_eng.pdf`（132 頁，2025-10-24）；桌面 `Mapping/` | 風險對策 → NIST CSF 2.0 子類別，E187 以章節名引用 | 高（政府），但對 CSF 非 ISO、E187 非獨立欄 | 不灌，AI 初配提示 |
| SEMI 官方（確認無對照） | SMCC《E187 Compliance Guidance》白皮書 | SEMI SMCC WG1／2 | `Manufacturing_Cyber_Security_White_Paper_FINAL070725.pdf`（18 頁，2025-07）；桌面 `Mapping/`；數位產業署《查核規範》v1.0（23 頁）桌面 `Semi E187/` | 12 條 RQ 的 rationale 與範例 | 高，但**全文零他框架引用** | 不灌 |
| IEC 62443 → 800-53 R5 | ① NIST SP 800-82 r3 附錄 ② ISAGCA 62443↔CSF ＋ NIST CSF↔800-53 xlsx ③ OSA `iec-62443.json` | ① NIST ② ISA 全球資安聯盟＋NIST ③ 社群 | ① nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-82r3.pdf（**待取得**）② isagca.org ＋ 桌面 `Mapping/csf-pf-to-sp800-53r5-mappings.xlsx` ③ https://github.com/opensecurityarchitecture/osa-data 的 `data/framework-coverage/iec-62443.json`，repo `scripts/data/osa_iec62443_to_sp800-53r5.json`（桌面無此檔，runner 直接取自 GitHub） | 62443-3-3 SR → 800-53 | ① **高**（800-82 是 800-53 的 OT overlay）② 高但兩跳 ③ 中偏低 | ① `official` ②③ `third-party`；**沒有 ISA 或 NIST 直接發的官方對照**，三者都不含 62443-4-1 practice |

**公信力的實務判準**：「稽核報告引用這條時，客戶或驗證機構會不會問『這是誰說的』」。NIST、DoD、ENISA、METI 不會被問；OSA、SunSquare 會，所以它們只能是顧問看哪幾條的起點，顧問在工作台確認過轉 `curated` 才進報告，報告寫「顧問確認，參考來源 X」。三級分級的完整定義見 analysis §二。

**DEV 實際灌了哪幾冊**（2026-10-24 實查 `oscal.control_mappings`；`origin` 四值：`official`／`third-party`／`curated`／`ai-suggested`，二級來源自 CM-3068 起標 `third-party`，`curated` 保留給顧問覆寫與確認）：

| mapping | 來源 → 目標 | 控制項層 | AO 層 | origin | provenance 現況 |
|---|---|---|---|---|---|
| 90 | CMMC L2 → 800-171 r2 | 110 | 320 | official（規則生成） | 記規則文字 |
| 129 | CMMC L1 → 800-171 r2 | 17 | 59 | official（規則生成） | 記規則文字；L1 的 AO 以文字比對對應 L2 practice 序號 |
| 128 | 800-171 r2 → 800-53 R5 | 198 | 0 | official | 記「附錄 D」，**無網址** |
| 127 | 800-53 R5 → ISO 27001:2022 | 643 | 0 | official | 記 OLIR #155 檔名與日期，**無網址**；Clause 4～10 列 `remarks='clause'`、`status='disabled'` |
| 130 | CRA → 800-53 R5 | 179 | 17 | third-party | 記 OSA 檔名、GitHub 網址、CC BY-SA 4.0 |
| 131 | SEMI E187 → IEC 62443 | 26 | 0 | third-party | 記「SunSquare 2026-07-08 Table 2」與 CSV 檔名，**無文章網址** |
| 132 | IEC 62443 → 800-53 R5 | 65 | 0 | third-party | 記 OSA 檔名、GitHub 網址、CC BY-SA 4.0 |
| 267 | CMMC L2 → ISO 27001:2022 | 229 | 684 | ai-suggested＋curated | AI 初配（CM-3066 跑出）；這冊是直達路線，有了它 CMMC→ISO 不必經 800-53 |
| 268 | CRA → ISO 27001:2022 | 64 | 0 | ai-suggested | AI 初配 |

三冊 official／third-party 的 provenance 缺網址（128、127、131），報告引用時無法一鍵指路；補法是在灌資料腳本的 provenance 加 `url` 後重跑（冪等，只更新不新增），屬小修，要不要開卡由決策者裁。

**踩到會怎樣**：跳得越多對得越鬆。ISO 一條常對 800-53 多條、800-53 一條又對 800-171 多條，三跳後一條 CMMC 控制項可能對到 ISO 四五條，含雜訊；因此官方鏈第一版關係一律 `intersects-with`、信心留空，靠顧問在工作台確認或 AI 補關係型別。路線不是寫死的：顧問或 AI 直接建一冊「CRA ↔ ISO」後，解析服務會走一跳直達，800-53 自動退為備援。來源分級與二級來源的可信度判準見 `docs/analysis/2026-10-24-mapping-sources-for-frameworks-without-official-crosswalk.md` §二。

### 解析規則（套件 `ControlMappingResolutionService`）

輸入：來源框架版本 id、來源 `control_id`、來源 `part_id`（可空）、目標框架版本 id 清單（空＝全部）。輸出：`[(目標框架版本, 目標 control_id, 目標 part_id 或 None, relationship, confidence, origin, 幾跳)]`。

1. 只看 `status='active'` 且沒被 `overrides_entry_id` 指到的列（被蓋的官方列自動退場）。
2. 直連優先：`source→target` 或 `target→source` 反向都算（對照雙向等價）。
3. 先找 AO 層（`source_part_id` 相符）；沒有才找控制項層，控制項層命中時回傳 `target_part_id=None` 並把 `confidence` 乘 0.7（常數放 `common/constant`，可調）。
4. 多跳（CMMC→800-171→800-53→ISO）：最多三跳，每跳 `confidence` 連乘，`relationship` 取最弱（equivalent-to > superset/subset > intersects-with），回傳 `hops`。
5. `not-related-to` 是明示排除，命中即從結果拿掉，不往下跳。控制項層的排除涵蓋該控制項全部評估目標；反過來，排除了某控制項的任一評估目標，同一控制項的控制項層命中（「全部評估目標適用」）也不再成立。少了後半條，評估目標列逐條標不可用擋不住多跳的控制項層退路，只能寫跨層排除列。

### 匯入匯出（套件 `OscalIoService` 擴充＋`ControlMappingExcelService`）

- **OSCAL JSON**：`mapping-collection` 1.2.2 形狀；`mapping-resource-reference.href` 寫框架版本 uid；`mapping-item.type` 用 `control` 或 `part`；`provenance` 必填照 D1。匯入時框架版本 uid 對不到 → 400 列出缺哪個。
- **Excel／CSV 固定範本**（下載空白範本）：欄位 `source_framework_code, source_version, source_control_code, source_part_id, target_framework_code, target_version, target_control_code, target_part_id, relationship, confidence, remarks`。匯入時 `control_code` 不存在於該框架 catalog（參考框架除外）→ 整檔退回列出錯行，不半寫。
- **官方檔轉接器**（卡 1 一次性，放主專案 `scripts/`）：OLIR xlsx（20 sheet、A 欄 800-53、D 欄 ISO，Clause 條文列先存著標 `remarks='clause'`）、`NIST_800-53_to_800-171.xlsx`（B 欄逗號多值拆列）、CMMC↔800-171 規則生成（practice `XX.Ln-a.b.c` → `a.b.c`，AO `_obj.N` 同序號 equivalent-to）。三者 `origin='official'`、`relationship='intersects-with'`（官方不給型別，CMMC↔171 例外為 equivalent-to）、`confidence` NULL、`method='manual'`、`provenance` 記檔名與日期。
- 匯入語意照 D6；「還原出廠設定」是獨立 API `POST /oscal/control-mappings/factory-reset` 帶 `confirm=true`。

### 證據沿用（主專案，卡 3a／3b／3c）

**共用讀模型** `infra/readmodel/audit/evidence_reuse_candidate_query.py`：
- 專案 → `module_frames.oscal_framework_version_uid` → 來源框架版本（專案 clone 的 catalog `framework_version_id` 是 NULL，**必須走 module_frame 回溯**，2026-10-24 實查五個專案皆可回溯）。
- 本專案每個 AO（`workflow_execution_control_mapping` 的 `control_id`／`ao_part_id`，取當前輪）→ 套件解析 → 目標代號清單（含 relationship／confidence／origin／hops）。
- 同租戶（D11）、user 選定的來源專案：其 `workflow_execution_control_mapping` 命中目標 `control_id`（AO 層命中再縮 `ao_part_id`）→ `job_evidences`（`is_deleted=false`、排除 `source='REUSED'` 避免循環、排除本專案已有同 `file_id` 的）。
- 輸出每列：本專案 AO、來源專案、來源控制項／AO、relationship、confidence、origin、hops、證據清單（uid、檔名、描述、上傳時間、來源專案該 AO 的最新判定供參考）、`default_checked`（D9 規則）。
- 單一 `job_execution_id` 版本＝同一支加 filter，供 drawer。

**API**（`api/flow_control/routes/evidence_reuse_route.py`；2026-10-25 改判後的形狀，舊的 `sources`／`preview`／`reuse`／`new-since-last` 由新端點取代或改語意）：
- `GET /projects/<uid>/evidence-reuse/sources`：**可見**（D11）、未封存、未刪、非本專案的來源，每筆帶框架、可對應 AO 數、證據檔數、`authorization_boundary_description`（living SSP 的 `ssp_system_characteristics`）、`reused_count`（已沿用檔數／AO 數）、`cited_count`（已被判定引用）、`last_adjusted_at/by`、`can_pick`（沒有 AO 層對照者 false 並附原因）。首頁列表與選擇頁共用。
- `GET /projects/<uid>/evidence-reuse/<source_uid>/tree`：來源框架導航（群組→控制項→AO，各節點證據檔數）與每檔的「本專案預設歸檔」（對照表配到的 AO 層通用列）與「其他可能」（部分可用）。
- `PUT /projects/<uid>/evidence-reuse/<source_uid>` body＝期望的最終集合 `[{evidence_uid, job_execution_uid, mapping_entry_id|null}]`；BE 算差集：新增寫、拿掉軟刪、被判定引用的拿不掉回在 `blocked`；回 `{added, removed, blocked}` 並寫一筆紀錄事件。
- `DELETE /projects/<uid>/evidence-reuse/<source_uid>`：全部取消，同樣跳過被引用並回報；寫紀錄事件。
- `GET /projects/<uid>/evidence-reuse/<source_uid>/history`：該來源的調整紀錄（稽核事件 `EVIDENCE_REUSE_ADJUSTED`／`EVIDENCE_REUSE_CLEARED`，payload 帶數字）。
- `GET /jobs/<uid>/evidence-reuse`、`POST /jobs/<uid>/evidence-reuse/reuse`：drawer 單筆，維持。
- 權限：專案 manager 或任務指派人（既有 project-role 守門）；頁面入口與自動分類同 `PROJ.EVIDENCE_MANAGE`。

**寫入**：每筆新開 `job_evidences`：同 `file_id`、`source='REUSED'`、`reused_from_evidence_id`、`mapping_entry_id`、`description` 帶「沿用自〈專案〉〈控制項 AO〉」。同任務同 `file_id` 已存在（既有唯一索引）→ 跳過並計入摘要。檔案本體不複製：沿用列直接指向同一份檔，不經過來源那筆證據。

**刪除與來源移除**（決策者 2026-10-26 裁）：
- **刪除只動本專案，從不連帶其他專案。** 任務刪除前先打 `GET /job-evidence/<uid>/delete-impact`，回本專案同一輪同一份檔（只比 `file_id`，不擴到同內容另存的檔）另掛的評估目標，以及其他專案名。其他專案只告知、不動。
- **本專案多處掛同一份檔時，使用者選範圍**：`DELETE /job-evidence/<uid>?scope=this|project`。`project`＝本專案當前輪同一份檔的全部有效證據，逐筆照現行守門（被指派人／專案 manager、輪次未結束），擋下的留著並在 `kept` 回報。前端 `job-drawer/JobEvidenceDeleteDialog.vue`，只有本專案另有掛載時才出兩個選項。
- **沿用來的那筆被任務刪除＝本專案此處不適用**：寫 `deleted_reason='NOT_APPLICABLE'`（自己上傳的照舊 `USER_DELETED`）。任務層候選、一鍵沿用、橫幅「有新證據」與寫入前比對都排除這組（任務, 檔案）；調整頁不排除，照常列出、目標標 `dismissed`、不預設勾，勾回照常寫入。**陷阱**：調整頁拿掉、全部取消、歷史清理也都是 `USER_DELETED`（DEV 當前輪有 3,738 筆），拿它當不適用會讓整輪沿用建議一起消失，所以不適用必須是獨立的原因值。
- **來源被刪，下游照常可用**：檔案從不因刪證據被刪（軟刪；`job_evidences.file_id` 對 `upload_files` 有外鍵，還有人指著就刪不掉）；硬刪來源專案只刪該專案自己的證據列。下游標籤改「沿用自〈專案〉（來源已移除）」，來源那筆軟刪、硬刪或來源專案已刪都算（`reused_source_removed`，任務抽屜與判定頁共用同一支純量 SQL）。
- **檔案只有一份、不複製**：上傳後內容不可改，複製與關聯只差儲存空間；複製會打掉 sha256 去重與「也掛在 N 處」，所以不做。唯一會改判的情況是客戶要求「刪專案時連檔案一起徹底清掉」，那時要在沿用當下複製，不是刪除時才複製。
- **跨租戶**：來源只從「使用者看得到的專案」取（RLS 擋跨租戶，程式層擋同租戶看不到的組織單位）；寫入前後端重算候選，前端偽造的證據 uid 寫不進去。`job_evidences` 本身沒開 RLS，靠 join 有 RLS 的表擋；評估過開 RLS，背景與系統脈絡路徑太多，沒做。

**Migration**（主線 `phase=active`，**回寫時明講出貨基線待重產**）：`job_evidences.source` CHECK 加 `REUSED`；加 `reused_from_evidence_id INT`、`mapping_entry_id BIGINT`（soft-ref）；`compliance.projects` 加 `last_evidence_reuse_at TIMESTAMPTZ`；`job_evidences.deleted_reason` CHECK 加 `NOT_APPLICABLE`（`2026-10-26-fr162-job-evidences-not-applicable.sql`）。

**FE 專案層（2026-10-25 改判，取代 3b 的規劃頁對話框）**：路由比照自動分類：`…/round/:roundUid/evidence-reuse`（首頁列表）、`…/evidence-reuse/new`（選擇來源專案）、`…/evidence-reuse/:sourceUid`（三欄調整頁，新增與調整同一頁）。新目錄 `src/views/evidence-reuse/`；三欄用 PrimeVue `Splitter`（`state-key` 存欄寬）；右欄複用 `evidence-classification/components/PlacementTable.vue` 與 `AOPickerDialog.vue`。總覽工具列的「自動分類證據」「證據雲端資料夾」兩顆與新項合併成「證據作業」下拉（`ProjectAuditorOverview.vue`），各項顯示條件維持原本。規劃頁 `EvidenceReuseBanner.vue`／`EvidenceReuseEntryButton.vue`／`EvidenceReuseDialog.vue` 移除。授權邊界預設三行、展開全文，畫面不標「SSP」。雛形 `prototype/project-reuse-page.html`。
**FE 3c 任務層**：`job-drawer/JobEvidenceReuseSection.vue`，證據區動作列加**常駐按鈕「沿用」**（與上傳、加連結並排，正式入口、永遠在）＋提示列（只是提醒，有可沿用才出現）＋展開清單勾選沿用；全部沿用完或沒有對應時按鈕仍可按，清單處顯示原因並指路框架對照頁；已沿用證據在既有清單 Tag `lang.grc_shared.reused_from_project`（比照 `inherited_from_prev_round`），點 Tag 顯示來源專案與該處判定；AR 判定頁證據清單同 Tag。

### 控制項關聯管理：現行規則（頁面名稱已改「控制項關聯管理」）

本段是目前的規則，與下方「矩陣視圖」原設計衝突時以本段為準。

- **只能同層對**：控制項對控制項、評估目標對評估目標。拖曳只能拖到同一欄、同一層的另一列，放下就是「搬」（新格寫直連、原格標不可用），沒有複製。後端在寫入一處把關（`add_entry`）：主軸評估目標寫到目標**控制項**的直連（清空、詳情對話框的確認／改狀態／標不可用都走這條）一律展開成對該控制項每個評估目標各一條；其餘跨層寫入回 `GRC_400189`。前端各入口不必各自處理層級。
- **控制項層有對應、評估目標層沒細分**＝目標控制項的全部評估目標都適用（證據沿用同規則）。評估目標列直接把它們全部列出（虛線膠囊，來源／狀態／信心照控制項層那條），**只是顯示、不寫資料**。該列一旦細分到某目標控制項的部分評估目標，那個控制項的控制項層命中就不再算全部適用（矩陣與沿用共用 `refined_targets`）。
- **陷阱**：解析器只要該評估目標有任何評估目標層列，就不再退回控制項層，帶出的那組會一起消失。所以在帶出的膠囊上刪除、搬移，或在該列新增，前端都先呼叫 `POST /oscal/control-mappings/entries/expand` 把整組寫成逐條 curated（關係與信心照搬），再做原本的動作。
- **人工新增**一律「通用」（equivalent-to）、信心 1，不讓使用者填。新增走共用挑選器（見下），可多選。
- **每格**：膠囊 ×＝移除（寫一條主軸↔目標「不可用」直連，多跳的中繼不動）；格尾「＋」新增、垃圾桶「清空這格」；都靠右。清空在控制項列＝對每個目標控制項寫控制項層不可用；在評估目標列＝真的評估目標層膠囊照它寫、承繼膠囊送目標控制項由後端展開，寫出來全是評估目標對評估目標。
- **還原出廠設定**（維護選單）：主軸 × 選一欄，只留隨套件出貨的官方與第三方；顧問與 AI 列停用不刪，並清掉 `overrides_entry_id`（解析器把被任何列指到的列當退場，只停用不會讓官方列回來）。
- **暫時隱藏**：「產生 AI 建議」對所有人隱藏（含超級管理員）、OSCAL 匯入匯出與「一對框架檔」匯入隱藏；開關在 FE `controlMappingUtil.js`（`AI_SUGGEST_ENABLED`／`OSCAL_IO_ENABLED`），後端端點與能力點守門照舊。
- **工具列兩列**：上列搜尋、篩選（只在目前那段顯示列數）、排序；下列全部展開、欄寬（自動符合＝依內容、平均分配＝框架欄等寬，主軸欄都不吃剩餘寬）、全螢幕｜批次作業（下載當前主軸框架設定、空白範本）、維護（匯入關聯表、還原出廠設定；整顆按 `v-can` manage）。分段按鈕一律共用元件 `SegmentButtons`（照待辦任務頁、small）。
- **關聯表 Excel**：匯出主軸對全部其他框架；第 1～3 列標題與說明、第 4 列中文表頭、第 5 列（隱藏）是匯入用機器鍵、第 6 列起資料；狀態寫「通用／部分可用」、來源寫中文。匯入找第 5 列的鍵（舊版第 1 列相容），比對用畫面狀態（避免把 subset／superset 改寫成等價）。匯出再匯入零變動（DEV 實測 1,490 筆全略過）。
- **共用挑選器 `AOPickerDialog`**（控制項關聯管理新增對應、證據沿用加入評估項目、AI 自動分類三處共用）：左欄群組 → 控制項（點已展開的群組收合）、右欄勾選（控制項標題三態框）、上方搜尋（搜全部）與「全部／只看已勾」、底列已勾膠囊。群組代號 `ISO27001-A5` 而名稱已是「A.5 …」就不顯示代號（挑選器內統一套）。標題「從 〈框架〉 挑選，對應到 〈這一條〉」；評估目標字母從文字開頭 `[x]` 取（`_obj.N` 不是字母序）。

### 對照維護工作台：矩陣視圖（主專案，卡 2）

**為什麼是矩陣不是一對一**（決策者 2026-10-24 裁）：一對一配對數隨框架數平方成長（5 框架 10 對、10 框架 45 對、加 3 個參考框架 78 對），顧問被迫同一條控制項對不同框架看好幾次。資料層不變（對照庫仍存邊，任意一對都能解析），只改畫面組織。

- 路由 `control-mapping`，側選單掛「合規稽核」群組（`ui_routes` migration），能力點 `oscal.control-mapping.read`／`manage`。
- **主軸 × 並排欄**：頂部選一個主軸框架（任選，記上次）、勾要並排看的框架（每個一欄）；**參考框架（800-53、800-171、62443）預設隱藏**，系統背後用它們算多跳。主表一列一條主軸控制項（左欄 sticky），可 ▸ 展開到評估目標（只在主軸側展開，目標側不展開）；每格列出對到的目標代號膠囊（關係色點＋來源小標），**超過兩顆收成「+N」**；空格顯示「—＋」，一眼看出主軸比該框架多要什麼。
- **編輯在膠囊上**：點膠囊 → **置中對話框**（PrimeVue `Dialog modal`，寬約 60rem；決策者 2026-10-25 裁捨棄抽屜，雛形 `prototype/mapping-side-panel.html`）。由上到下七塊：①標題（控制項對應／評估目標對應）②**兩端並排**——左是點的那列、右是對到的，各顯示框架、代號、標題，可展開條文全文與評估目標清單（AO 層對應預設展開命中那條並標「評估目標 N／M」）③**狀態**：只有「通用」「部分可用」兩種顯示值＋一句補充（見下）④**來源**：官方寫文件名（NIST OLIR #155／800-171 附錄 D／DoD CMMC 模型）、第三方寫機構與檔名附連結、AI 寫日期與理由、顧問寫人名時間備註；信心分數放在來源行一起看：第三方（OSA coverage 換算、SunSquare 0.6）、AI（附理由）、顧問（覆寫時可填）顯示；官方不顯示（NIST／DoD 不給、DB 為 NULL，不顯示成「未給」） ⑤**路徑**：每跳一列（代號、框架、該跳來源），直連一跳；中繼框架只在這裡出現 ⑥**動作**：確認這條對應／改為通用（或部分可用）／標為不可用；被顧問改過的多「撤銷，回到官方」⑦變更紀錄。**不出現「間接」「無法修改」字樣**，每顆都能改（改＝在兩端建 curated 直連，中繼不動，9a 端點）。空格「＋」新增（目標框架已定，只選目標控制項／AO）。
- **狀態顯示值與資料值的換算**（決策者 2026-10-25 定名）：畫面只顯示**通用**（綠點：`equivalent-to`，以及 `superset-of`／`subset-of` 從寬往窄沿用的方向）與**部分可用**（橘點：`intersects-with`、涵蓋從窄往寬的方向、官方未給強度的全部）。**不可用**不是顯示狀態（`not-related-to` 的格子不會出現東西），只當對話框的動作名。顧問改狀態時選項只有「通用」「部分可用」「不可用」三個，分別存 `equivalent-to`／`intersects-with`／`not-related-to`；涵蓋兩值只從官方資料進來，顧問不手選。等價／部分重疊／涵蓋／不相關這些術語只留在資料庫、Excel 與 OSCAL 匯出，畫面不出現。證據沿用預覽只對「AO 層通用」預設勾選。
- 篩選：只看「無對應的列」「有 AI 建議待確認」；搜尋主軸代號／標題。
- 工具列按「主軸 × 選定欄」：匯入／匯出**寬表**（Excel 一個 sheet：主軸一欄＋每個框架一欄，格內多值以分號分隔）、空白範本、產生 AI 建議（選哪一欄或全部已勾的欄；初配控制項／配對評估目標）。**OSCAL 匯出系統自動拆成每對框架一份 mapping-collection**；OSCAL 匯入一次一份。「重建」改為對「主軸 × 一欄」做，二次確認。
- BE 端點：`GET /oscal/control-mappings/matrix?axis_fv=&cols_fv=&expand_ao=&filter=`（一次回整張矩陣：主軸控制項與 AO、每格的生效對應含路徑；93×3 欄量級一次查得完，解析走套件 `resolve` 批次版）；寬表匯入匯出 `GET|POST /oscal/control-mappings/wide?axis_fv=&cols_fv=`；其餘端點（override／disable／history／ai-suggest）不變。
- **主軸欄凍結在左（sticky）、右側欄固定寬可橫向捲動**：欄數再多也不擠扁，主軸永遠看得到；FE 用 PrimeVue DataTable `frozen` 欄或自訂 sticky。禁左側粗色邊框；`dark-card`、全站字級。雛形 `prototype/mapping-workbench.html`。

### AI 建議配對（主專案，卡 4）

兩種模式共用一支 plugin `core/plugins/_control_mapping_ai_suggest.py`，皆走 `@register_job_handler`、`gateway.complete`（`select_model_for_current_tenant`）、`_ai_quota_adapter` 配額、prompt 由 `jedi_ai_gateway.get_prompt("control-mapping", <key>, BUILTIN)` 取；worker 另起。

- **模式 B 控制項層初配** `control_mapping_ai_suggest_controls`，payload `{source_framework_version_id, target_framework_version_id, hint_pairs?}`：對來源每條控制項（標題＋statement prose＋繁中）與目標候選控制項兩兩比，回 relationship（五值）＋confidence＋reason，寫控制項層 `origin='ai-suggested'`；mapping 不存在先建（`method='tool-assisted'`）。候選縮小優先序：① `hint_pairs`（顧問在工作台勾「大概相關」，或由 ENISA CRA 報告／第三方 E187↔62443 對照整理成提示）；② 無提示時兩階段：先讓 AI 從目標框架 group 選相關 group，再比 group 內控制項；③ 總對數 ≤ 5,000 直接全配。第一版要跑：CRA 的 Article 13／Annex VII 17 條↔ISO、62443-4-1 五個 practice↔ISO／800-53（其餘由 1b 灌的 OSA 與 SunSquare 二級來源接）。
- **模式 A 評估目標層** `control_mapping_ai_suggest_objectives`，payload `{mapping_id, only_missing}`：只對 origin 為 official／third-party／curated 的 active 控制項層列跑（ai-suggested 的要顧問確認後才跑）；每列兩邊 AO prose 一次 call，要求**每對都回一筆**（含 not-related-to）；寫 AO 層 `origin='ai-suggested'`，同鍵已有 curated 跳過。
- **規則回填控制項層關係**（模式 A 每列跑完）：來源控制項 AO 全部各有一個 equivalent-to → 控制項層 equivalent-to、confidence 取平均；全部 not-related-to → not-related-to；其餘 intersects-with、confidence 取有對應者平均。寫成新 `ai-suggested` 列 overrides 原 official 列，不改官方列。
- AI 回非 JSON 或代號不在範圍 → 該列記 error 繼續不中斷；progress 每列回報。
- 工作台：「產生 AI 建議」下拉兩項（初配控制項／配對評估目標）；「AI 建議」篩選；「確認」轉 curated（新列 overrides 指回 AI 列；2a 的 `override_entry` 若不接受不改內容只確認，加 `confirm_entry`）。

**AO 配對實例**（DEV 真實資料）：CMMC `AC.L2-3.1.1` 6 個 AO ↔ ISO A.5.15／5.16／5.18 共 7 個 AO，42 對一次給 AI。期待：`_obj.1`「authorized users are identified」↔ `A.5.16_obj.3`「each identity is unique and linked to an individual」等價 ~0.9；`_obj.4`「access limited to authorized users」↔ `A.5.15_obj.4`「access control rules are implemented」等價 ~0.85；`_obj.2`／`_obj.5` 程序身分 → 不相關（ISO 無此概念）；`_obj.3`／`_obj.6` 設備 ↔ `A.5.15_obj.4` 部分重疊 ~0.6。存的是代號對代號，prose 只是配對輸入。

### Error code（主專案 `common/code/flow_control_error_code.py`，GRC_ 前綴照序）

| 碼 | 情境 |
|---|---|
| `GRC_400xxx` CONTROL_MAPPING_IMPORT_INVALID | 匯入檔欄位／代號對不到，msg 帶錯行清單 |
| `GRC_404xxx` CONTROL_MAPPING_NOT_FOUND | mapping 或 entry 不存在 |
| `GRC_409xxx` CONTROL_MAPPING_PAIR_EXISTS | 同一對框架版本已有 mapping |
| `GRC_412xxx` CONTROL_MAPPING_REBUILD_NOT_CONFIRMED | 重建未帶 confirm |
| `GRC_409xxx` EVIDENCE_ALREADY_IN_JOB | 沿用時同檔已在該任務 |
| `GRC_400189` CONTROL_MAPPING_CROSS_LEVEL | 對照跨層寫入（評估目標→控制項的直連會自動展開，不會走到這裡） |
| `JOB_EVIDENCE_400001` JOB_EVIDENCE_DELETE_SCOPE_INVALID（`common/code/error_code.py`） | 刪除範圍不是 `this`／`project` |

FE `error-code.json` 中英同步。

### 權限

工作台屬系統層（沒有「專案」對象，FR-160 判準），兩顆能力點 `is_platform=false`、scope `{tenant,org_unit}`，`license_module='module-frame'`。收證據沿用沿用任務既有守門。

## 拆分表 {#split nav="拆分"}

| 卡 | 批 | Repo | 做什麼 | 改哪些檔 | 驗收 | 依賴 |
|---|---|---|---|---|---|---|
| 1 套件對照表與匯入匯出 | 1／2 | jedi-python-package ＋ BE（DI、轉接腳本） | 021／022 migration、entity／repo／mapper／service、解析服務、OSCAL 與 Excel 匯入匯出、三份官方檔轉接、DEV 灌資料 | 套件 `jedi_oscal_v2/{migrations,domain,infra,app}/...mapping...`；BE `di_containers/oscal/oscal_containers.py`、`scripts/import_official_control_mappings.py` | DEV 兩表有資料（CMMC↔171 AO 層、171↔53、53↔ISO）；匯出 JSON 符合 1.2.2；重匯不重複；解析 A.5.15 回得到 CMMC AO | 無 |
| 5 OSCAL 版本標記 | 1 | jedi-python-package | `catalog_service.py:90` 改 1.2.2；023 回填 migration | 套件兩檔 | DEV metadata 全 1.2.2 | 無（與卡 1 不同檔） |
| 2 對照維護工作台 | 3／5 | BE 一張、FE 一張（Repo 不同拆兩卡：2a BE、2b FE） | 2a：route、能力點、ui_routes migration、匯入匯出端點、error code；2b：頁面、匯入匯出 dialog、i18n、capabilities.js | BE `api/oscal/routes/control_mapping_route.py`、`app/oscal/service/control_mapping_app_service.py`、`scripts/sql/2026-10-xx-fr162-control-mapping-capabilities.sql`；FE `src/views/control-mapping/*`、`router/index.js`、`capabilities.js`、locales | 選 CMMC L2↔ISO 看到列與 AO；改一條→DB 新 curated 列、官方列 disabled；匯出再匯入一致 | 卡 1 |
| 3 證據沿用 | 4／6／7 | 3a BE；3b FE 專案層；3c FE 任務層 | 3a：共用讀模型、五支 API、migration（job_evidences 三處＋projects 一欄）、error code；3b：規劃頁橫幅＋三步 dialog；3c：drawer 區塊、Tag、AR 頁 Tag | BE `infra/readmodel/audit/evidence_reuse_candidate_query.py`、`app/flow_control/service/evidence_reuse_app_service.py`、`api/flow_control/routes/evidence_reuse_route.py`、`scripts/sql/2026-10-xx-fr162-job-evidences-mapped.sql`；FE `views/project/planning/EvidenceReuseBanner.vue`、`EvidenceReuseDialog.vue`、`job-drawer/JobEvidenceReuseSection.vue`、`evidenceMapper.js`、AR 證據清單元件 | 見分工概述卡 3 列；DB `source='REUSED'` 筆數＝摘要沿用數；來源專案刪一份檔，新專案那筆仍可預覽 | 卡 1；3b、3c 等 3a |
| 4 AI 建議配對 | 7 | BE ＋ FE 小改（同卡，FE 只加下拉與篩選） | 兩種模式 handler、prompt、入列端點、規則回填；工作台「產生 AI 建議」下拉、origin 篩選、「確認」 | BE `core/plugins/_control_mapping_ai_suggest.py`、route；FE 工作台頁兩處 | CRA↔ISO 初配有列；確認後 AO 層有列；控制項層關係被回填；只跑一次 only_missing 新增 0 | 卡 2b |

七批（以 Notion 子卡 Stage 為準）：批 1 ＝ 1a、5（套件不同檔）。批 2 ＝ 1b。批 3 ＝ 2a。批 4 ＝ 3a。批 5 ＝ 2b（順便建 `mappingTags.js` 共用 Tag 元件）。批 6 ＝ 3b。批 7 ＝ 3c、4。原規劃把 2a／3a 同批、2b／3b／3c 同批，重查發現它們各自要在同一個登記型檔（error code 表、migration manifest、FE api.js／locales／error-code.json）加列，同批合回必衝突，故拆開；工作台先、沿用後，決策者最早在批 5 看到畫面。**工作坊規範「一張卡只改一個 repo」**，所以卡 1 拆 1a（套件）／1b（BE），卡 2 拆 2a（BE）／2b（FE），卡 3 拆 3a（BE）／3b（FE 專案層）／3c（FE 任務層）。

## 端到端驗收 {#e2e nav="驗收"}

1. DEV 有 CMMC L2 專案 A（已交若干證據）與 ISO 專案 B，同資訊系統。
2. 工作台選 CMMC L2 → ISO 2022，看到 AC.L2-3.1.1 對 A.5.15／A.5.16／A.5.18（經 800-171、800-53 三跳，hops=3），展開 AO 層有 AI 建議或顧問確認的配對。
3. 建好 B 進規劃頁，頂部橫幅「偵測到 CMMC L2 專案 A 有 N 個評估目標的證據可沿用」；點進去選 A，預覽表每列有對應、關係、信心、證據數，等價且信心高的預設勾；按沿用，摘要「沿用 N 份到 M 個 AO，K 個無對應」，無對應清單可下載。
3b. 之後在 A 的 AC.L2-3.1.1 [d] 再上傳一份新證據，B 規劃頁橫幅「有 1 份新證據可沿用」；B 的 A.5.15 [b] drawer 證據區提示列「其他專案有 1 份符合的證據」，單筆沿用成功、清單帶「沿用自 CMMC 專案」。
4. B 送審、AR 判定頁該證據帶同樣標籤。
5. 工作台把 A.5.15 [b]↔AC.L2-3.1.1 [d] 改成 intersects-with，匯出 Excel 看到 curated 列；重匯官方 OLIR 檔，curated 列仍在、官方列數不變。

## 不做 {#notdo nav="不做"}

- 一專案多框架（D4）。
- CRA、SEMI E187 到 ISO 的「官方」等級對照（CRA 官方只對 Common Criteria、SEMI 沒有）；第一版靠二級來源（`origin='third-party'`，報告標來源）＋AI 補洞，不會有「官方」等級。
- ISO Clauses 4～10 建模（OLIR 的 204 列條文對應先存著標 `clause`，等 ISO 導入設計拍板）。
- 自動從 NIST／ENISA 網站抓更新。
