FR-139 · 設計定案 · 2026-10-14
缺失與修改建議欄、建議附件、訪談表累積匯入、資源庫版本、資源庫與專案一對一、送審鎖版
顧問在合規資源庫把客戶的訪談結果寫完整——實施狀態、現況、備註、缺失、缺失修改建議,需要時在建議下面附一份可直接給客戶用的範本(建議附件)。訪談表可以反覆匯入、一次一次累積;每整理一輪就存一版,可看舊版、可比對、可整份還原。啟動專案時專案直接看資源庫那一份 SSP,規劃頁除任務設定外唯讀;顧問按「送審」那一刻存一版並凍結給稽核輪,之後資源庫就不能改,只有輪次在「規劃中」才可編輯。本文件寫定案規則、為什麼這樣定、踩到會怎樣。
狀態:設計定案(2026-10-14),待開卡|討論稿:
discussion.md|雛形:prototype/index.html(四頁:控制項填寫、版本歷史、版本比對、專案規劃頁唯讀)
雛形是假資料畫面,欄位與選項以本文件為準(實施狀態選項以 §6.6 的六值為準)。檔案路徑與行號以 2026-10-14 為準,查不到時以函式名重新定位。路徑慣例:BE 路徑相對
compliance-manager-be/;pkg/代表~/Projects/Jedicogy/module/jedi-python-package/;FE 路徑相對compliance-manager-fe/。
名詞:資源庫=合規資源庫(compliance.module_frames);範本 SSP=資源庫底下那份 SSP(module_frames.template_ssp_id),也就是工作副本;AO=評估目標(assessment objective),控制項底下的檢查項目;IR=控制項實施紀錄(oscal.ssp_implemented_requirements);props=OSCAL 物件上的「名稱+值」擴充屬性陣列;新模式專案=living_ssp_id 等於某資源庫 template_ssp_id 的專案;舊模式專案=啟動時複製了一份 SSP 的既有專案。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-14 | v1:D1~D21 設計定案;資料模型、版本操作、權限分流、匯入兩路徑、畫面甲案;三個子需求十一張卡拆分與端到端驗收 | — |
| 2026-10-18 | D19 改判:資源庫只在綁定專案最新一輪 planning 時可改,送審後到結案(含改善中、已結案)一律鎖 |
CM-2888 |
| 2026-10-14 | 開卡前補定七條:資源庫終身只綁一個專案(不分進行中/結案/封存);匯入前自動存備份版、匯入後不另存;鎖寫區間含 pending_reverify、remediation 與 closed 解鎖;版本不提供刪除;看版本用 oscal.resource-library.read;實施狀態 system_menus seed 併入卡 1;雛形兩處與決策不符不修、以本文件為準 |
母卡 CM-2745、子需求 CM-2746~2748、子任務 CM-2749~2759(T-1~T-10、T-2b;守門分流從卡 1 併入 T-9) |
合規資源庫是顧問替客戶導入法規框架的第一個入口。顧問到客戶現場訪談,手上拿的訪談表就是系統可下載的「現況資料 Excel」;回來把訪談表匯入,同一份表隨訪談次數反覆匯入。之後逐條整理控制項與 AO 的實施狀態、現況說明、備註。
整理到「部分實作」這類條目,顧問要寫出缺了什麼與怎麼補,手上若有可直接給客戶用的範本就附上——這一步就是正式稽核前的事前稽核。以 CRA 的漏洞通報條為例:
| 欄位 | 顧問寫的內容 |
|---|---|
| 實施狀態 | 部分實作 |
| 現況說明 | 只有提供 E-Mail 收信接收通報 |
| 備註 | 連 email 都未開通 |
| 缺失 | 無通報平台、也沒有公告、也沒有通報手冊 |
| 缺失修改建議 | 提供通報平台網址、官網公告通報方式、提供手冊下載 |
| 建議附件 | 通報流程手冊範本.pdf(說明:照這份補上收件、分級、修補、回覆四段) |
缺失若沒有自己的欄位,就只能塞進現況說明:一個欄位同時講「現在是什麼」和「少了什麼」,產缺失報告得人工再拆一次(FR-138 自家 CRA 自評就是這樣)。同一份訪談表重新匯入,還可能把上次填好的欄位清掉(§6.5)。
流程由顧問主導:
%%{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
actor C as 顧問
participant RL as 合規資源庫<br/>(範本 SSP=工作副本)
participant V as 版本索引
participant P as 專案
participant A as 稽核輪
C->>RL: 下載訪談表、到客戶現場訪談
C->>RL: 匯入訪談表(只蓋檔案有填的欄)
RL->>V: 匯入前自動存一版當備份(source=import-backup)
loop 整理現況、缺失、建議、附範本
C->>RL: 暫存(不開版)
C->>RL: 儲存成新版本
RL->>V: 存一版(source=manual)
end
C->>P: 啟動專案(不複製,直接指向範本 SSP)
P-->>RL: 規劃頁唯讀讀取同一份
loop 持續訪談
C->>RL: 再匯入、再整理、再存版
RL-->>P: 專案即時看到
end
C->>A: 送審(=啟動稽核)
A->>V: 存一版(source=submit,記 audit_round_id)
A->>A: 凍結快照給稽核輪
Note over RL: 送審後到結案資源庫鎖寫
C->>A: 覆核送審(同一套:存版、凍結、鎖寫)
整案一句話:顧問在合規資源庫裡把訪談結果寫完整(現況、備註、缺失、修改建議、附範本),每整理一輪就存一版;顧問覺得可以了就送審,送審那刻鎖一版給外部稽核用。專案只看資源庫那一份,不能改。
| 子需求 | 做什麼 | 產出 | 完成怎麼判定(決策者親手做什麼、看到什麼) |
|---|---|---|---|
| 一、訪談欄位與匯入(卡 1、2、2b、3、4) | 控制項與 AO 加「缺失」「缺失修改建議」;建議下方加「建議附件」;訪談表兩條路徑加三欄;重複匯入只蓋檔案有填的欄 | props 新鍵、共用讀寫 helper、整條控制項批次存 API、建議附件表與 API、FE 欄位、Excel 三欄、實施狀態選項進 system_menus、jedi-oscal-v2 合併規則 |
在資源庫 460 某條控制項填缺失與建議、在建議附件掛一份範本並寫說明,存檔重開還在,且程序書區與程序書文件池看不到它;下載訪談表看到三個新欄,只填缺失欄重新匯入,其他欄沒被清掉 |
| 二、資源庫版本(卡 5、6、7) | 暫存、儲存成新版本、匯入自動存版、版本歷史、唯讀看舊版、比對、整份還原 | 版本索引表、存版/暫存/列表/比對/還原 API、版本歷史頁、比對頁 | 改兩條控制項、存兩版;開第 1 版是唯讀;比對第 1、2 版只標出那兩條的差異;還原到第 1 版,確認框列出會改動的筆數與「目前工作副本會自動存成新版」,確定後歷史多一列,再還原回去內容一字不差 |
| 三、一對一與唯讀(卡 8、9、10) | 啟動專案指向範本 SSP;一個資源庫終身只綁一個專案;複製資源庫;專案規劃頁唯讀(任務設定照舊可編);每次送審鎖版;送審後鎖寫、只有規劃中可改 | 啟動流程改寫(先盤點複製鏈)、複製資源庫、守門分流、規劃頁唯讀、送審存版 | 用資源庫啟動專案後,在資源庫改一條現況,專案規劃頁立刻看到;規劃頁控制項與 AO 欄位全灰、任務指派與到期日仍可改;送審後資源庫存檔鈕不能按、版本歷史多一列「送審」並標對應稽核輪;輪次進整改、結案後仍不能存檔 |
依賴:子需求一、二可平行;子需求三的送審鎖版用到子需求二的存版。畫面最先出的是資源庫控制項欄位(卡 2)與建議附件(卡 2b)。
| 項 | 定案 | 理由 | 被排除 |
|---|---|---|---|
| D1 缺失與修改建議存哪 | OSCAL props 兩個新鍵 gap、recommendation(ns 見 D14),控制項 IR 與 AO statement 兩層都有 |
與「實施狀態」「現況說明」走同一條路;啟動、凍結、存版都是整列複製,props 自動帶走;OSCAL 允許自訂 props | 套件加實體欄(OSCAL 標準沒有這欄,要動 schema);主專案側表(每條複製路徑都要手動補,程序書池就曾因此漏帶);直接寫評估結果或 POA&M(啟動稽核才生成,資源庫階段沒有容器) |
| D2 備註 | 就是兩表既有的 remarks,不新增欄位 |
欄位本來就在;專案規劃頁控制項存檔沒送 remarks(ProjectPlanningView.vue:1443)的漏洞,規劃頁唯讀後自然消失 |
— |
| D3 建議附件 | 獨立一張表 compliance.ssp_control_attachments,掛 IR 或 AO statement;不進程序書池、程序書池不加種類欄、jedi-compliance-audit 不改;存版、還原、複製資源庫時跟著複製,稽核凍結不帶;畫面放在「缺失修改建議」正下方、與「程序書」區塊分開;AO props 舊的 reference-documents JSON 不碰 |
程序書是客戶自己的 SOP、用來證明現況,要進稽核凍結;建議附件是建議的一部分,客戶照做完就沒用,不該混進稽核基準。同池靠種類欄過濾,稽核側每個讀程序書的地方都要多一個條件,漏一處建議範本就跑進稽核 | 程序書池加「文件種類」欄(稽核側讀取處逐一加過濾、要改套件);用檔名或描述欄約定(查不了、篩不了) |
| D4 訪談表欄位 | 路徑 A(完整匯入)與路徑 B(控制項現況九欄)都加「備註」「缺失」「缺失修改建議」三欄,匯出同步帶出;docx 匯入不動 | 訪談表就是現況資料 Excel,顧問在現場直接填;兩條路徑共用欄名定義,只改一處兩份表欄位就不一致 | docx 一併加(docx 不在顧問訪談流程內) |
| D5 匯入四個坑全收 | ① 套件 update 模式 props 逐鍵合併:檔案有的鍵才蓋;② 資源庫完整匯入用合併語意、不清空重建;③ 實施狀態值域收進 system_menus 當唯一來源(D20);④ props 讀寫 helper 收成一支共用,匯出、差異比對、合併決策同步認得新鍵 |
不修①②,第二次匯入只填缺失欄,上次的現況說明就被清掉;不修③,下拉、匯入、匯出各說各話;不修④,新鍵要改六處,漏一處就是某條路徑看不到缺失 | — |
| D6 誰能編範本 SSP | 具備資源庫編輯權即可(D18),拿掉「只有帳號層超級管理員」的守門(common/authz/ssp.py:215);「顧問」角色之後再定義。這條守門目前走不到(雙模式解析器沒接上),實作是在範本 SSP 的所有寫入入口要求資源庫編輯權 |
顧問是主要編輯者,現行守門是開發期暫時做法 | 先建「顧問」角色再開權限(角色定義牽涉授權模型,會卡住整案) |
| D7 專案規劃頁 | 資源庫帶來的資料除任務設定外全部唯讀:控制項與 AO 實施層欄位、程序書、建議附件、其他 SSP 頁籤;任務設定(指派、到期日、說明)照舊可編。FE 可編判斷與 BE 專案側 SSP 寫入守門一起改 | 客戶專案管理者只指派工作與上傳證據;SSP 是顧問的成果,兩邊都能改就互相覆蓋 | 只在畫面做唯讀(共用端點擋不住,見 §6.4) |
| D8 資源庫與專案 | 一對一。啟動專案時 living_ssp_id=template_ssp_id,不複製三件組;啟動時擋「這個資源庫已綁定過任何專案」(不分進行中、結案、封存;新年度一律走複製資源庫);資源庫有專案綁著不能刪;新年度走「複製資源庫」(D17);資源庫沒有公版——公版是框架層概念,module_frames.scope 欄留著、預設不開放、畫面不出現公版資源庫。卡 8 動手前必須先盤點啟動專案的複製鏈並回報 |
顧問啟動後仍持續訪談,只有指向同一份專案才能即時看到;複製一份就得設計同步,那是兩份真相 | 維持複製另做同步(雙向衝突與同步時機);一個資源庫多專案共用一份 SSP(不同客戶的現況互相覆蓋);資源庫層加公版旗標(與框架層重複) |
| D9 送審 | 送審=啟動稽核。依序「存版(source='submit'、audit_round_id)→ 凍結快照給稽核輪 → 資源庫鎖寫」;覆核輪(close_out)送審走同一套;稽核流程本身不改 |
外部稽核看的是送審那一刻的內容,顧問要能回頭看送審時寫了什麼;主稽核與覆核各對應哪一版,要能從兩頭查到 | 稽核中仍允許改工作副本(規劃頁直接指向工作副本,客戶會看到與稽核中內容不一致);覆核另做一套 |
| D10 版本放哪 | 版號與修訂史寫 OSCAL 原生 metadata.version、metadata.revisions(revision 的 links 帶 rel=predecessor-version);舊版內容用套件 snapshot_ssp 整份複製成獨立 SSP;只多一張索引表 compliance.ssp_versions,設計成「SSP 的版本」(owner_type+owner_id),第一版只有資源庫用;OSCAL 各表不加版本欄 |
匯出的 SSP 自帶版本資訊;整份 SSP 快照讓看舊版、比對、還原都直接用既有編輯器與 diff | 只寫 metadata(做不到回顧、比對、還原);存 JSON 文件快照(還原要走匯入路徑,看舊版與比對要多一層轉換);SSP 主表加 group_id/version_no(FR-019 早期做法,v2 已移除) |
| D11 版本操作範圍 | 快照是整份 SSP(主表、12 子表、程序書掛載、建議附件、風險);暫存不開版、儲存成新版本先暫存再快照、匯入前自動存一版當備份(匯入後不另存);唯讀開舊版;版本不提供刪除;看版本歷史與舊版要 oscal.resource-library.read;比對接既有 diff 的「SSP 對 SSP」入口,只比控制項與 AO 實施層(狀態、現況、備註、缺失、建議、程序書掛載、建議附件);還原=整份換回去,先列會改動筆數給顧問確認,確定後把目前工作副本存成一版(pre_restore)再換,永遠可逆;稽核凍結快照不進索引表 |
顧問在乎控制項層的內容變化,比對先做這一層避免範圍膨脹;逐項還原會讓工作副本變成哪一版都不是 | 逐項還原;比對擴到其他頁籤 |
| D12 控制項填寫畫面 | 甲案:控制項與 AO 表單各加兩個文字框,建議下方加建議附件區塊,其他七個頁籤不動;BE 照樣做「整條控制項連 AO 批次存」API 給暫存用 | 先讓資料有地方放,畫面改版等顧問實際用過再決定 | 這次重新設計填寫區 |
| D13 範本 460 | FR-138 塞在「現況說明」的缺失,新欄位上線後由小卡(卡 4)拆到「缺失」欄 | 460 是第一個真實案例,拆完正好驗證新欄位 | — |
| D14 props ns 與 name | ns=https://guidant.ai/ns/oscal,name=gap、recommendation;全產品之後的自訂 props 用同一個 ns |
OSCAL 規定自訂 props 帶自家 ns 避免與標準名稱撞 | — |
| D15 建議附件分類 | 不分類,只有檔案加一行說明,不進 system_menus |
與程序書已分表,「放哪張表」就是分類,沒有讀取端要用種類 | 建議附件分種類 |
| D16 兩張新表的 schema | compliance.ssp_versions、compliance.ssp_control_attachments;判準是 model 寫在哪個套件就放哪個 schema,兩張 model 都在主專案;跨 schema FK 指 oscal.system_security_plans 是正常的(先例 project_audit_rounds),FK 寫完整 schema 名 |
放 oscal 會讓人以為是 jedi-oscal-v2 的表 |
放 oscal |
| D17 複製資源庫 | 把啟動專案現有的複製邏輯(套件 clone_resource_library 三件組+程序書池、表單設定、ssp_risks)搬成「複製資源庫」,再補帶建議附件;版本歷史不帶;啟動專案不再用它 |
複製路徑與現行啟動專案幾乎相同,搬過來等於重用 | 匯出 Excel 再匯入(帶不走程序書檔、依賴匯入坑修完);資源庫加公版旗標 |
| D18 資源庫編輯權 | =oscal.resource-library.update,不另立能力點。範本 SSP 的所有寫入入口都要求它,含 SSP 共用端點:依「此 SSP 是否為某資源庫的 template_ssp_id」分流,是就走資源庫守門(能力點+稽核中鎖寫),不是才走專案守門 |
只守資源庫自己的端點,客戶專案管理者仍能從共用端點改到顧問的 SSP | 另立新能力點 |
| D19 何時可改 | 只有綁定專案最新一輪是「規劃中」(planning,任務規劃與任務執行階段)可改;送審後到結案(含 remediation、closed)一律鎖。覆核子輪回到 planning 時可改,子輪送審後再鎖。判斷用白名單(只放行 planning),新增輪次狀態時預設是鎖 |
專案看到的 SSP 就是這一份,專案一開始稽核,客戶與稽核方看到的內容就不能再被改;新年度要改走複製資源庫 | 進整改就解鎖(顧問邊整改邊訪談)——結案後資源庫仍可任意改,專案那份 SSP 跟著變;等結案才解鎖 |
| D20 實施狀態值域 | 六值 implemented/partial/planned/alternative/not_applicable/unknown(=還沒訪談),key 保留底線拼法;進 system_menus group ssp_implementation_status;FE 下拉、Excel 匯入驗證、BE 驗證三處讀同一份;匯出 OSCAL 時才轉標準拼法 not-applicable |
DEV 現存 implemented 70,914、unknown 2,133、partial 47、not_applicable 13、alternative 2、planned 2;底線拼法散在讀取端與七萬多筆資料,硬改風險大、使用者無感;unknown 是顧問需要的真實狀態 |
key 對齊 OSCAL 連字號並 migration 既有資料 |
| D21 既有資料 | 舊模式專案不遷移,維持各自獨立的 SSP 副本、照舊在專案側可寫;一對一檢查只算新模式專案;STG/POC 上線前查有無一資源庫多專案,列進上版檢查項 | DEV 資源庫 460 綁 3 個專案、427 綁 1 個,全是舊模式;遷移要決定哪個專案接手範本 SSP,對既有稽核輪沒有收益 | 遷移既有專案 |
| 元件 | 現況 | 本案動作 |
|---|---|---|
| 實施狀態、現況說明 | props implementation-status、implementation-description;控制項在 ssp_implemented_requirements.props、AO 在 ssp_statements.props(app/oscal/service/ssp_control_implementation_service.py;資源庫側 app/module_frame/service/module_frame_control_default_service.py、module_frame_control_objective_default_service.py) |
欄位不動;值域收進 menu(D20) |
| 備註 | 兩表 remarks |
就是「備註」(D2) |
| AO 識別 | statement_id 格式 <control>_obj.N |
沿用 |
| 同一控制項多筆 IR | _ir_for_control(ssp_control_implementation_service.py:888-900)挑「已有說明」那筆 |
新欄位讀寫、批次存、比對走同一挑選 |
| props 讀寫 helper | 六份複製:ssp_control_implementation_service.py:960-989、資源庫兩支 default service、export/ssp_v2_content_loader.py:203、ssp_resources_context_service.py:197、import_diff/snapshot_views.py:43 |
收成一支(D5④) |
| 程序書池 | oscal.ssp_reference_documents+掛載表 ssp_reference_document_mappings(control_implementation→IR.id、objective→statement.id),model 在 jedi-compliance-audit;凍結時 ssp_document_pool_service.py:91 clone_pool_for_snapshot 補複製 |
不動;存版、還原、複製資源庫複用 clone_pool_for_snapshot |
| 訪談表路徑 A | app/oscal/service/ssp_excel_import_app_service.py;欄位 app/module_frame/excel_template/sheet_definitions.py:452-494、中文欄名 header_i18n.py:127-136;寫入 import_adapter/excel_to_oscal_ssp.py:62-110;資源庫模式 mode="replace" 清空重建(:448-510) |
加三欄;資源庫模式用合併語意 |
| 訪談表路徑 B | app/oscal/service/ssp_control_impl_import_service.py,端點 /ssp/<ssp_uid>/control-implementations/import;逐列 update、_setp 逐鍵合併;欄名與資源庫共用 module_frame_template_import_service.py:92;模板版本檢查 excel_parser/version_check.py |
加三欄 |
| 套件 update 模式 | pkg/jedi-oscal-v2/.../oscal_io_service.py:1308 IR/statement 的 props 整份覆寫;None 跳過、清空要傳 "" |
逐鍵合併(D5①) |
| 實施狀態值域 | 三處不一致:FE 下拉寫死在 ModuleFrameTemplateEditView.vue:348-368;Excel 匯入 module_frame_template_import_service.py:79-86;SQL 註解 scripts/sql/2026-06-02-erd-chinese-comments.sql:720 |
三處讀 menu(D20) |
| 範本 SSP 寫入守門 | common/authz/ssp.py:215 _require_admin_for_template 只准超級管理員,但雙模式解析器沒接上(di_containers/oscal/oscal_containers.py:345),實際走不到;資源庫寫入靠 route 能力點+service 直接拿 ssp_id 寫 |
兩類入口都要求 oscal.resource-library.update(D18) |
| 專案 SSP 寫入守門 | common/authz/ssp.py:99-103,187-211 專案管理者+可寫判斷;可寫判斷 domain/oscal/service/ssp_project_resolver.py:111-133(凍結一律鎖;living 在無輪次或當前輪 planning 可寫) |
新模式專案改走資源庫守門 |
| SSP 歸屬判斷 | 生效的是 ssp_project_resolver.py:97-108:先查 projects.living_ssp_id、再查 project_audit_rounds.ssp_id |
範本判斷以 template_ssp_id 直比(§6.4) |
| 資源庫編輯頁 | src/views/module_frame/ModuleFrameTemplateEditView.vue(2,246 行、八頁籤),路由 /module-frame/:uid/template-edit |
只掛新子元件 |
| 專案規劃頁 | src/views/project/ProjectPlanningView.vue(2,609 行),canEditSsp = apStatus === 'planning'(:158) |
唯讀旗標與提示子元件 |
| 啟動專案 | app/project/service/project_start_app_service.py:347 呼叫套件 clone_resource_library 複製三件組;:378 寫 living_ssp_id;:385-389 _init_ssp_control_implementations 補建控制項;程序書池與 ssp_risks 手動補複製(:231-299, :392-409) |
不再複製、直接指向;複製鏈先盤點(卡 8) |
讀 living_ssp_id 的地方 |
11 支(flow_control/project_service.py、cloud_integration/project_tree_loader.py、task_alert/*、oscal/ssp_*_app_service.py、export/ssp_v2_content_loader.py 等) |
照常讀;逐支確認不寫 |
| 複製資源庫 | 不存在:FE i18n 有 from_sample: "複製資源庫" 但無畫面使用,BE 無端點 |
新功能,搬啟動專案的複製邏輯(D17) |
| 稽核凍結 | 套件 AuditRoundAppService.launch_audit(pkg/jedi-compliance-audit/.../audit_round_app_service.py:402)snapshot_ssp 複製 living SSP 到 project_audit_rounds.ssp_id 並補程序書池;主專案由 app/flow_control/service/oscal_stage_handlers.py 的 planning 階段推進呼叫;輪次狀態 planning → audit_planning → auditing → remediation → pending_reverify → closed;覆核由 launch_reverify 另開 close-out 子輪 |
送審多存一版(卡 10) |
| 整份複製 | pkg/jedi-oscal-v2/.../snapshot/oscal_snapshot_service.py:52 snapshot_ssp(SSP+12 子表+一份 metadata,status=frozen)、:105 clone_resource_library、:188 deep_clone_ssp |
存版用 snapshot_ssp |
| 差異比對 | app/oscal/service/import_diff/ssp_diff_service.py(四態 unchanged/changed/added/gone、七區;:725 annotate_against_snapshot)、decision_merge.py:107 merge_by_decisions |
加「SSP 對 SSP」入口 |
| SSP 主表與 metadata | v2 主表只有 id/uuid/metadata_id/status/import_profile_id;oscal.metadata 有 version、revisions JSONB,主專案沒用 |
開始寫(D10) |
① props 新鍵(IR 與 AO statement 都有):
| name | ns | 值 | 說明 |
|---|---|---|---|
gap |
https://guidant.ai/ns/oscal |
純文字 | 缺失:少了什麼 |
recommendation |
https://guidant.ai/ns/oscal |
純文字 | 缺失修改建議:怎麼補 |
規則:空字串=刪掉該鍵(與 _setp 一致);讀取時沒有該鍵視為空;全部欄位空時 props 回 None(與 _map_to_props 一致)。匯出 docx/Excel、差異比對、合併決策都認得這兩鍵,由共用 helper 一次處理。
② 表 compliance.ssp_control_attachments(建議附件;主線 migration,GRANT cm_app 含 sequence,RLS 依租戶):
| 欄 | 型別 | 用途 |
|---|---|---|
id/uid/tenant_id/org_unit_id |
標準欄 | RLS 依租戶 |
context_type |
varchar(32) NOT NULL | control_implementation(掛 IR)或 objective(掛 AO statement) |
context_id |
bigint NOT NULL | IR.id 或 statement.id |
file_id |
bigint NOT NULL | 指向 upload_files |
description |
text | 一行說明 |
created_user/created_at/updated_user/updated_at |
審計欄 | API 回傳補 *_user_name |
③ 表 compliance.ssp_versions(版本索引;主線 migration,GRANT cm_app 含 sequence,RLS 依租戶):
| 欄 | 型別 | 用途 |
|---|---|---|
id/uid/tenant_id/org_unit_id |
標準欄 | |
owner_type |
varchar(32) NOT NULL | 擁有者種類,第一版只有 module_frame |
owner_id |
bigint NOT NULL | 資源庫 id |
ssp_id |
bigint NOT NULL | 這一版的整份快照 SSP(snapshot_ssp 產出,status=frozen);FK → oscal.system_security_plans |
version_no |
int NOT NULL | 同一擁有者遞增 1、2、3… |
version_label |
varchar(64) | 顯示版號,同步寫 metadata.version |
source |
varchar(32) NOT NULL | manual(手動存版)/import(匯入觸發)/submit(送審鎖版)/pre_restore(還原前自動存版) |
note |
text | 存版說明 |
audit_round_id |
bigint 可空 | 送審版對應的稽核輪(project_audit_rounds.id);其他來源為空 |
created_user/created_at |
審計欄 | API 回傳補 created_user_name |
唯一鍵 UNIQUE (owner_type, owner_id, version_no)。
④ OSCAL 原生版本欄(不加欄,只開始寫):每存一版,該版快照與工作副本的 metadata.version 寫版號字串(與 version_label 同步);metadata.revisions 追加一筆 {version, last-modified, title, remarks, links:[{rel: "predecessor-version", href: "#<前一版 SSP uuid>"}]}。
⑤ public.system_menus 實施狀態 seed(主線 phase=seed,ON CONFLICT DO NOTHING,先例 scripts/sql/2026-05-22-seed-oscal-ssp-roles.sql):
| group | key | 顯示(zh-tw) |
|---|---|---|
ssp_implementation_status |
implemented |
已實作 |
ssp_implementation_status |
partial |
部分實作 |
ssp_implementation_status |
planned |
規劃中 |
ssp_implementation_status |
alternative |
替代措施 |
ssp_implementation_status |
not_applicable |
不適用 |
ssp_implementation_status |
unknown |
未評估 |
FE 走 API.SYSTEM_MENU,i18n 走 lang.ssp_implementation_status.<key>;Excel 匯入合法值與 BE 驗證讀同一份;匯出 OSCAL 時 not_applicable 轉 not-applicable。
| 層 | 是什麼 | 誰改 | 誰看 |
|---|---|---|---|
| 工作副本 | 資源庫的範本 SSP,永遠是最新 | 顧問(稽核中鎖寫) | 顧問;新模式專案規劃頁即時唯讀 |
| 版本歷史 | 某時點的整份快照(ssp_versions 每列一份),建立後不再改 |
沒人 | 顧問回顧、比對、還原來源 |
| 稽核輪凍結快照 | 每次送審凍結給該輪的那份(project_audit_rounds.ssp_id) |
沒人 | 稽核輪 |
送審時存的版(source='submit')與稽核凍結是兩份不同的快照,靠 audit_round_id 互指:前者給顧問回顧比對、帶建議附件;後者給稽核輪當基準、不帶建議附件。稽核凍結快照不進索引表。
| 操作 | 做什麼 | 開版嗎 |
|---|---|---|
| 暫存 | 全頁未存的控制項與 AO 一次存進工作副本(整條控制項連 AO 批次存 API) | 否 |
| 儲存成新版本 | 先暫存;再 snapshot_ssp 複製工作副本、依 IR/statement 新舊 id 對照補程序書池掛載、建議附件與 ssp_risks;索引表加一列;寫 metadata.version 與 revision |
是(manual) |
| 匯入前備份 | 匯入確認前自動存一版,匯入後不另存 | 是(import-backup) |
| 看舊版 | 開版本快照,既有編輯器帶唯讀旗標;版本索引裡的 SSP 與 status='frozen' 一律不可寫 |
— |
| 比對 | 兩版之間或某版對工作副本;只列有差異的控制項與 AO,四態著色;比對欄位:狀態、現況、備註、缺失、建議、程序書掛載、建議附件 | — |
| 還原到某版 | 先算出各頁籤會改動的筆數給確認框(「會改動:控制項 N 條、負責單位 M 筆……」);確定後把目前工作副本存成一版(pre_restore),再把目標版整份複製成新工作副本,module_frames.template_ssp_id 與一對一專案的 living_ssp_id 同時換過去 |
是(pre_restore) |
| 送審 | 見 §6.4 | 是(submit) |
存版、還原都是寫入,受資源庫編輯權與稽核中鎖寫管(§6.4)。
範本 SSP 的寫入入口有兩類,規則要在兩類都成立:
| 入口 | 現況 | 本案 |
|---|---|---|
資源庫自己的端點(api/module_frame/__init__.py) |
route 能力點 oscal.resource-library.*,service 直接拿 ssp_id 寫 |
沿用能力點;寫入前加稽核中鎖寫判斷 |
SSP 共用端點(系統特性、元件、外部服務、責任人員、控制項現況等,經 SspPermissionChecker) |
一律走專案分支 | 先判「此 SSP 是否為某資源庫的 template_ssp_id」(直接比對,不靠 profile 反查):是→資源庫守門(oscal.resource-library.update+稽核中鎖寫);否→專案守門照舊 |
送審後鎖寫:範本 SSP 綁定的新模式專案,其當前輪次不是 planning 時(audit_planning、auditing、remediation、pending_reverify、closed),範本 SSP 的所有寫入回 412(D19);覆核子輪回到 planning 時可改、送審時再鎖。
唯讀的 SSP:版本索引裡的 SSP 與 status='frozen' 的 SSP 一律唯讀;讀取入口要認得版本快照(目前它既不是 living_ssp_id 也不是稽核輪 ssp_id,走共用端點會「找不到」)。
專案側:新模式專案的規劃頁,SSP 資料一律唯讀(D7);專案管理者帳號直接打寫入 API,因範本 SSP 走資源庫守門而被擋。舊模式專案維持專案守門與既有可寫判斷(D21)。
| 路徑 A:完整匯入 | 路徑 B:控制項現況 | |
|---|---|---|
| 入口 | ssp_excel_import_app_service.py |
ssp_control_impl_import_service.py,/ssp/<ssp_uid>/control-implementations/import |
| 新欄 | 分頁「適用控制項」加「備註」「缺失」「缺失修改建議」(sheet_definitions.py、header_i18n.py、excel_to_oscal_ssp.py) |
九欄加三欄(欄名與資源庫共用 module_frame_template_import_service.py) |
| 模板版本 | 欄位變動要升模板版本,excel_parser/version_check.py 認得新版 |
同左 |
| 合併規則 | 資源庫模式不清空重建;套件 update 模式 props 逐鍵合併——檔案有填的欄才蓋,空白欄保留現值 | 既有 _setp 逐鍵合併,新欄比照 |
| 實施狀態驗證 | 讀 ssp_implementation_status menu |
同左 |
| 匯出 | 帶出三個新欄 | 同左 |
新模式專案指向範本 SSP,匯入一律從資源庫做;舊模式專案的專案側 mode="update" 照舊(D21)。docx 匯入不動。
雛形:prototype/index.html(控制項填寫、版本歷史、版本比對、專案規劃頁唯讀)。
資源庫「適用控制項」頁籤:
ssp_implementation_status 六值。ModuleFrameTemplateEditView.vue 已超標,新欄位、建議附件區塊、頁首鈕一律做成新子元件,原檔只加一行掛載。版本歷史頁:列版號、來源(手動存版/匯入自動存版/送審鎖版/還原前自動存版)、說明、誰、何時;送審版標對應稽核輪(主稽核或覆核)並顯示鎖頭;每列可檢視(唯讀)、比對、還原。還原確認框列出會改動的筆數,並說明目前工作副本會先存成新版。
版本比對頁:選兩版(或某版對工作副本),只列有差異的控制項與 AO,四態著色。
專案規劃頁(新模式專案):控制項與 AO 實施層欄位、程序書、建議附件、其他 SSP 頁籤全部唯讀;頁首一行「內容即時同步自合規資源庫」,有資源庫編輯權的人多一個「前往合規資源庫」連結;任務設定照舊可編。ProjectPlanningView.vue 已超標,唯讀旗標與提示做成子元件。
依「決策者何時看得到畫面」排序。e2e 屬母卡收口,不在單卡做。只動一支套件 jedi-oscal-v2(卡 3),開發期走 path dependency,發版等決策者明示。
| 卡 | 做什麼 | 主要檔案 | 依賴 | 驗收條件(決策者親手做什麼、看到什麼) | 會踩的鐵則 |
|---|---|---|---|---|---|
| 卡 1 BE 新欄+共用 helper+批次存 API | props gap/recommendation 讀寫;六份 helper 收一支(保留空字串刪鍵、全空回 None);整條控制項連 AO 批次存端點;範本 SSP 兩類寫入入口要求 oscal.resource-library.update(依 template_ssp_id 分流) |
ssp_control_implementation_service.py、資源庫兩支 default service、export loader、snapshot_views.py、ssp_resources_context_service.py、decision_merge.py、common/authz/ssp.py |
— | 與卡 2 一起驗 | 先查再寫(helper 收斂不另寫第七份);common/authz/ 當次跑 reviewer;守衛測試 |
| 卡 2 FE 控制項欄位+暫存鈕 | 甲案兩層表單各加兩框;頁首暫存鈕接批次存;實施狀態下拉讀 menu(seed 在卡 3,卡 2 先接 API) | ModuleFrameTemplateEditView.vue 只掛新子元件 |
卡 1 API 介面先定 | 在範本 460 填一條控制項與其 AO 的缺失、建議,按暫存後重新整理,內容都還在 | 檔案體積上限(只准拆);下拉不寫死 |
| 卡 2b 建議附件 | compliance.ssp_control_attachments migration;上傳/列表/改說明/刪除 API(權限同 D18);FE 區塊放缺失修改建議正下方 |
新 domain/infra/app service、新 route、FE 新子元件 | 卡 1 | 在 460 某條控制項與其 AO 各上傳一份並寫說明,重新整理還在;程序書區與「程序書文件池」頁籤都看不到它 | migration 鐵則(日期註解、GRANT、schema_migrations、只套 DEV、回報出貨基線待重產);審計欄位 enrich |
| 卡 3 Excel 三欄+狀態選項+套件合併規則 | 兩路徑加三欄(匯入、匯出、欄名、模板版本);ssp_implementation_status seed 與 FE、Excel、BE 驗證讀 menu、匯出轉標準拼法;jedi-oscal-v2 update 模式 props 逐鍵合併;資源庫完整匯入不清空 |
sheet_definitions.py、header_i18n.py、ssp_control_impl_import_service.py、excel_to_oscal_ssp.py、ssp_excel_import_app_service.py、module_frame_template_import_service.py、pkg/jedi-oscal-v2/.../oscal_io_service.py、seed migration |
卡 1 | 下載 460 的訪談表看到三個新欄;只填某條的「缺失」重新匯入,該條現況說明與其他條都沒被清掉;實施狀態下拉與 Excel 驗證一致、有「未評估」 | jedi 套件異動(plan 列範圍與其他 consumer 風險、path dependency 不 commit、發版等令);system_menus 鐵則 |
| 卡 4 範本 460 缺失回填 | 把塞在現況說明的缺失拆到缺失欄,可半自動(列出含缺失字樣的條目給人確認) | DEV 資料作業 | 卡 1、卡 2 | 在 460 抽五條,現況說明只剩現況,缺失欄有拆出來的那段 | 只動 DEV |
| 卡 5 版本索引表+存版 API | compliance.ssp_versions migration;存版(暫存→snapshot_ssp→依 id 對照補程序書池、建議附件、ssp_risks→索引→metadata 版號與 revision);版本列表;匯入成功自動存版;版本快照讀取入口唯讀 |
新 service、ssp_document_pool_service.py:91 複用 |
卡 1、卡 2b | 與卡 6 一起驗 | migration 鐵則;@transaction 一個存版一個交易 |
| 卡 6 FE 版本歷史頁+存版鈕 | 頁首「儲存成新版本」(填說明)、版本歷史頁、唯讀開舊版 | 新頁+新子元件、ui_routes 登記 |
卡 5 | 改兩條控制項後存成新版本,版本歷史多一列且來源寫「手動」;點舊版打開,所有欄位不能改 | 新頁路由 name 要登記 ui_routes;檔案體積上限 |
| 卡 7 比對與還原 | 既有 diff 加「SSP 對 SSP」入口,只比控制項與 AO 實施層;整份還原:算會改動筆數→確認框→存 pre_restore→複製→換 template_ssp_id 與 living_ssp_id |
ssp_diff_service.py、還原 service、比對頁 |
卡 5 | 比第 1、2 版只看到那兩條的差異標記;還原到第 1 版,確認框列出「會改動:控制項 2 條……」與新版號;確定後歷史多一列,再還原回去內容一字不差、程序書與建議附件仍在 | 先查再寫(diff 用既有引擎不另寫);查記住舊 id 的快取或背景工作 |
| 卡 8 啟動改指向+一對一守門+複製資源庫 | 前置:先盤點 project_start_app_service.py 複製鏈,分「啟動要留/搬去複製資源庫/順手塞的」三類回報,回報後才動;啟動不再複製、指向範本 SSP;擋「已綁定過任何專案」(新 error code);有專案不可刪;複製資源庫(帶程序書池、建議附件、表單設定、ssp_risks,不帶版本歷史) |
project_start_app_service.py、資源庫刪除、新複製端點與列表動作 |
卡 2b | 盤點清單先交;從資源庫啟動專案,在資源庫改一條現況,專案規劃頁立刻看到;同一資源庫再啟動一次被擋(含該專案已結案或封存後);複製資源庫後新資源庫內容與兩種附件齊全、版本歷史是空的 | 分析後才改(盤點前置);先查再寫(搬既有複製邏輯) |
| 卡 9 規劃頁唯讀+守門調整 | 規劃頁資源庫資料唯讀(任務設定照舊可編)、頁首提示;歸屬判斷認得範本(template_ssp_id 直比),專案側 SSP 寫入守門擋新模式專案;確認 11 支讀 living_ssp_id 的地方無寫入 |
ProjectPlanningView.vue(只加唯讀旗標與提示子元件)、common/authz/ssp.py、ssp_project_resolver.py |
卡 8 | 新模式專案的規劃頁,控制項與 AO 欄位全灰、任務指派與到期日仍可改;用專案管理者帳號直接打寫入 API 被擋;舊模式專案行為不變 | common/authz/ 當次跑 reviewer;檔案體積上限 |
| 卡 10 送審鎖版+稽核中鎖寫 | 主稽核與覆核送審都走「存版(submit+audit_round_id)→凍結→鎖寫」;範本 SSP 兩類寫入入口加「當前輪不是 planning 就鎖」 |
oscal_stage_handlers.py、範本 SSP 寫入守門 |
卡 5、卡 8 | 送審後回資源庫,暫存與存版鈕不能按、版本歷史多一列「送審」並標對應主稽核輪;輪次進整改、結案後仍不能編輯;覆核送審後再鎖、多一列標覆核輪 | 狀態轉換前置條件;新 error code 命名並同步 FE error-code.json |
%%{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 S1[子需求一 訪談欄位與匯入]
C1[卡1 BE 新欄+共用 helper<br/>+批次存 API] --> C2[卡2 FE 控制項欄位<br/>+暫存鈕]
C1 --> C2b[卡2b 建議附件<br/>表+API+畫面區塊]
C1 --> C3[卡3 Excel 三欄+狀態選項<br/>+套件合併規則]
C1 --> C4[卡4 範本 460 回填]
end
subgraph S2[子需求二 資源庫版本]
C5[卡5 版本索引表+存版 API] --> C6[卡6 版本歷史頁<br/>+存版暫存鈕]
C5 --> C7[卡7 比對與還原]
end
subgraph S3[子需求三 一對一與唯讀]
C8[卡8 啟動改指向<br/>+一對一守門] --> C9[卡9 規劃頁唯讀<br/>+顧問編輯權]
C8 --> C10[卡10 送審鎖版<br/>+稽核中鎖寫]
end
C1 --> C5
C2b --> C5
C5 --> C10
上版檢查項:STG/POC 上線前查有無「一資源庫多專案」(D21);兩張新表與 seed 的 migration 照環境異動鐵則只先套 DEV,寫完回報出貨基線待重產。
用 DEV 一個 CRA 資源庫(建議 460),批次走完再一次回報:
🔴 範本 SSP 同時是專案的 SSP,唯讀只做在畫面上擋不住
指向後範本 SSP 的 id 就是專案的 living_ssp_id。所有走 SspPermissionChecker 的共用端點會把它判成專案 SSP,套「專案管理者+規劃階段可寫」——客戶專案管理者能透過這些端點改到顧問的 SSP。範本判斷要用 template_ssp_id 直比,命中就走資源庫守門。快照沿用來源的 profile(ssp_clone_service.py:129-131 整列複製根節點),若日後接上雙模式解析器、靠 profile 反查,快照會被判成範本而變可寫——所以不靠 profile。
🔴 重複匯入會清掉上次的內容
套件 update 模式 props 整份覆寫、資源庫完整匯入清空重建,第二次匯入只填缺失,現況說明就沒了。修完要拿「只填一欄重匯」實測,不能只看程式。
🔴 啟動專案後的初始化會作用在範本 SSP 上
_init_ssp_control_implementations(project_start_app_service.py:385-389)對「剛複製出來的新 SSP」補建控制項;指向後它會直接寫範本 SSP,程序書池與 ssp_risks 的補複製會變成自己複製給自己。卡 8 的盤點前置是硬性的。
| 陷阱 | 後果 | 對策 |
|---|---|---|
| props helper 有六份 | 漏改一處,那條路徑看不到缺失且不報錯;共用版沒保留「空字串刪鍵」「全空回 None」,清空欄位會存成空字串鍵 | 卡 1 收成一支並保留兩個行為 |
| 同一控制項多筆 IR | 缺失寫進 A 筆、畫面讀 B 筆 | 讀寫、批次存、比對都走 _ir_for_control |
| 守門只改一類入口 | 另一類入口就是漏洞 | 兩類入口同時套用能力點與鎖寫;卡 1 到卡 10 之間範本 SSP 沒有稽核中鎖,卡 8 上線前不會有新模式專案,可接受 |
snapshot_ssp 重產 IR/statement id |
程序書掛載與建議附件指向舊 id | 存版、還原、複製資源庫都複用 clone_pool_for_snapshot 的 id 對照,建議附件照同一份對照複製;任務與控制項對應(workflow_execution_control_mapping)用 control_id 字串+輪次,不受影響 |
snapshot_ssp 不含 ssp_risks |
版本快照少了風險 | 存版與還原另外補複製(啟動專案現行做法) |
| 還原換掉範本 SSP 的 id | 記住舊 id 的快取或背景工作讀到舊版 | template_ssp_id 與 living_ssp_id 同交易換;卡 7 查 task_alert 兩支等記 id 的地方 |
不可為空欄位(例 by_component.description) |
複製路徑寫入失敗 | 沿用套件既有處理 |
| 送審產生兩份快照 | 容易誤以為重複 | 刻意保留:送審版帶建議附件、稽核凍結不帶,靠 audit_round_id 互指 |
ModuleFrameTemplateEditView.vue 2,246 行、ProjectPlanningView.vue 2,609 行 |
往裡加會被 reviewer 判阻斷 | 新邏輯一律新子元件,原檔只加一行掛載 |
| 項目 | 去向 |
|---|---|
| 控制項填寫區重新設計(D12) | 全部做完、顧問實際用過再看 |
| 其他七個頁籤改動、版本比對擴到控制項以外(D11) | 另案 |
AO props 舊的 reference-documents JSON(D3) |
不碰 |
| docx 匯入加欄位(D4) | 不在顧問訪談流程內 |
| 「顧問」角色定義(D6) | 之後定義時把 oscal.resource-library.update 配進去 |
| 稽核流程本身的改動(D9) | 不做 |
| 既有一資源庫多專案資料遷移(D21) | 不做;上版前查 STG/POC |
| 版本歷史隨複製資源庫帶走(D8) | 不做 |
| 資源庫層公版與公版旗標(D8、D17) | 公版屬框架層 |
| 建議附件分類(D15) | 不做 |
| 逐項還原(D11) | 不做 |
| 缺失報告產出 | 卡 4 回填完成後另議 |