FR-139 · 討論稿 · 2026-10-14

顧問在資源庫訪談、存版、送審,專案只看不改

D1~D21 全部已裁;三個子需求、十一張卡;附四頁假資料雛形

這份把決策者 2026-10-14 裁定的 D1~D21 展開成可以開工的規格:控制項與評估目標多「缺失」「缺失修改建議」兩欄、建議下方多一塊「建議附件」(獨立一張表,不進程序書池)、訪談表重複匯入可以累積、資源庫有版本可存可比可整份還原、資源庫與專案改一對一且專案規劃頁唯讀、每次送審(主稽核與覆核)都鎖一版。每一項都附現況座標(程式碼行號或 DEV 唯讀查詢)。請先開 雛形索引 點五頁,再回來看規格。

D1~D21 全部已裁 上版前查一資源庫多專案 十一張卡,資源庫控制項畫面先出 動一支套件 jedi-oscal-v2

設計定案拍板後寫 design.md,本稿不再改寫。

狀態:討論稿(D1~D21 全部已裁,下一步寫 design.md、拆卡開 Notion) | 日期:2026-10-14 | 雛形:prototype/index.html

路徑慣例: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=控制項實施紀錄(ssp_implemented_requirements);props=OSCAL 物件上的「名稱+值」擴充屬性陣列。

§1

變更紀錄

日期 變更 對應
2026-10-14 初稿:已裁 D1~D13 展開、待補裁 P1~P8;資料模型草案、端到端流程、十卡拆卡草案、四頁雛形引用 —
2026-10-14 決策者審雛形與 P1~P8 後定案 D3、D7~D11、D14~D21、卡 2b

§2

分工概述(30 秒版)

整案一句話:顧問在合規資源庫裡把訪談結果寫完整(現況、備註、缺失、修改建議、附範本),每整理一輪就存一版;顧問覺得可以了就送審,送審那刻鎖一版給外部稽核用。專案只看資源庫那一份,不能改。

子需求 做什麼 產出 完成怎麼判定(決策者親手做什麼、看到什麼)
一、訪談欄位與匯入 控制項與 AO 加「缺失」「缺失修改建議」;建議下方加「建議附件」;訪談表兩條路徑加三欄;重複匯入只蓋檔案有填的欄 props 新鍵、共用讀寫 helper、整條控制項批次存 API、建議附件表與 API、FE 欄位、Excel 三欄、實施狀態選項進 system_menus、jedi-oscal-v2 合併規則修正 在資源庫 460 某條控制項填缺失與建議、在建議附件掛一份範本並寫說明,存檔重開還在,且下方程序書區與程序書文件池看不到它;下載訪談表看到三個新欄,只填缺失欄重新匯入,其他欄的內容沒被清掉
二、資源庫版本 暫存、儲存成新版本、匯入自動存版、版本歷史、唯讀看舊版、比對、整份還原 版本索引表、存版/暫存/列表/比對/還原 API、版本歷史頁 決策者親手改兩條控制項、存兩版;開第 1 版是唯讀;比對第 1、2 版,那兩條的欄位標出差異標記;按還原到第 1 版,確認框列出「會改動:控制項 2 條……」與「目前工作副本會自動存成第 3 版」,確定後歷史多一列第 3 版,再還原回去內容一字不差
三、一對一與唯讀 啟動專案改指向資源庫那份 SSP;一個資源庫只能有一個進行中專案;專案規劃頁唯讀(任務設定照舊可編);每次送審鎖版;稽核中鎖寫、進整改解鎖 啟動流程改寫(先盤點複製鏈)、複製資源庫(搬家)、四條守門調整、規劃頁唯讀、送審存版 用資源庫啟動專案後,在資源庫改一條現況,專案規劃頁立刻看到;規劃頁的控制項與 AO 欄位全是灰的、任務指派與到期日仍可改;送審後回資源庫,存檔鈕不能按、版本歷史多一列「送審」並標對應主稽核輪;輪次進整改後又能存檔

依賴:子需求一、二可平行;子需求三的送審鎖版用到子需求二的存版。畫面最先出的是子需求一的資源庫控制項欄位(卡 2)與建議附件(卡 2b)。

%%{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
圖 1 — 十一張卡的依賴(實線=必須先做)

§3

需求背景與使用情境

合規資源庫是顧問替客戶導入法規框架的第一個入口。顧問先到客戶現場訪談,手上拿的訪談表就是系統可以下載的「現況資料 Excel」;回來把訪談表匯入系統,同一份表會隨著訪談次數增加反覆匯入。之後顧問逐條整理控制項與 AO 的「實施狀態、現況說明、備註」。

整理到「部分實作」這類條目時,顧問要寫出缺了什麼與怎麼補,手上若有可以直接給客戶用的範本就附上。這一步其實就是正式稽核之前的事前稽核。以 CRA 的漏洞通報條為例:

欄位 顧問寫的內容
實施狀態 部分實作
現況說明 只有提供 E-Mail 收信接收通報
備註 連 email 都未開通
缺失(新) 無通報平台、也沒有公告、也沒有通報手冊
缺失修改建議(新) 提供通報平台網址、官網公告通報方式、提供手冊下載
建議附件(新) 通報流程手冊範本.pdf(說明:照這份補上收件、分級、修補、回覆四段)

流程改成顧問主導

  1. 顧問在資源庫訪談、匯入、整理,完成客戶的 SSP(系統安全計畫)。
  2. 啟動專案。客戶的專案管理者只負責指派工作與上傳證據,不再編輯 SSP。
  3. 顧問持續訪談、持續匯入,SSP 繼續在資源庫更新,專案即時看到。
  4. 顧問判斷可以請外部稽核了,按「送審」(就是現在的「啟動稽核」)。那一刻存一版、凍結一份給稽核輪;之後的覆核送審也走同一套。
  5. 之後走正常稽核流程,不改。

現況痛點

FR-138 自家 CRA 自評時,缺失沒有地方放,只能塞進「現況說明」:一個欄位同時講「現在是什麼」和「少了什麼」,要產缺失報告得人工再拆一次。同一份訪談表重新匯入,還可能把上次填好的欄位清掉(見盤點 ② 節)。


§4

現況盤點:接入點 × 現況 × 本案動作

① 欄位落點(資源庫範本 SSP 與專案 SSP 同結構)

接入點 現況 本案動作
實施狀態 存 props implementation-status;控制項在 ssp_implemented_requirements.props、AO 在 ssp_statements.props(app/oscal/service/ssp_control_implementation_service.py:40-41,93-94,864-865;資源庫側 app/module_frame/service/module_frame_control_default_service.py:36-43、module_frame_control_objective_default_service.py:1-10) 不動欄位;值域收斂(D5③)
現況說明 props implementation-description 不動
備註 兩表的 remarks 欄 就是本案的「備註」(D2)
缺失、缺失修改建議 沒有 新 props 兩鍵,控制項與 AO 都有(D1)
AO 識別 statement_id 格式 <control>_obj.N 沿用
同一控制項多筆 IR _ir_for_control 挑「已有說明」那筆(service :888-900) 新欄位讀寫走同一挑選
props 讀寫 helper 至少六份複製:ssp_control_implementation_service.py:960-989、module_frame_control_default_service.py:45,58、module_frame_control_objective_default_service.py:39,51、export/ssp_v2_content_loader.py:203、ssp_resources_context_service.py:197、import_diff/snapshot_views.py:43 收成一支共用(D5④)

DEV 唯讀查詢(2026-10-14),兩表 implementation-status 現存值:implemented 70,914 筆、unknown 2,133、partial 47、not_applicable 13、alternative 2、planned 2。

② 附件(程序書池與建議附件)

接入點 現況 本案動作
池 oscal.ssp_reference_documents(context_type='ssp'),欄位只有 id/uid/context_type/context_id/file_id/description/審計欄,沒有種類欄(DEV 查證);model 在 jedi-compliance-audit 套件 不動;套件不改(D3)
掛載 ssp_reference_document_mappings,context_type control_implementation→IR.id、objective→statement.id(app/oscal/service/ssp_document_pool_service.py:1-14) 沿用
實體檔 upload_files 沿用
AO 舊機制 props reference-documents 塞 JSON(service :44, :861-862) 不碰(D3)
兩線分工 FR-135 D7:程序書掛控制項或 AO、證據掛工作,兩線不混(docs/features/FR-135-2610-cra-compliance-framework/design-cra.md:379-389) 照舊
建議附件 沒有 新表 compliance.ssp_control_attachments,同樣掛控制項或 AO,但不進程序書池、不與證據混(D3)

③ 訪談表匯入(兩條路徑)

接入點 現況 本案動作
路徑 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(statement_id 隱藏/控制項識別碼/名稱/AO 編號/AO 名稱/實作狀態/現況描述/參考程序書/納入 Profile),沒有備註欄;寫入 import_adapter/excel_to_oscal_ssp.py:62-110 加三欄(D4)
路徑 A 資源庫模式 mode="replace" 清空重建(ssp_excel_import_app_service.py:448-510) 改合併語意(D5②)
路徑 A 專案模式 mode="update" upsert 新模式專案指向範本 SSP、不再從專案側匯入;既有舊模式專案照舊(D21)
路徑 B:控制項現況九欄 app/oscal/service/ssp_control_impl_import_service.py(欄位 docstring :9-10),端點 /ssp/<ssp_uid>/control-implementations/import(api/oscal/__init__.py:191);逐列呼叫 update、逐 key 合併(_setp);欄名與資源庫共用 module_frame_template_import_service.py:92;模板版本檢查 excel_parser/version_check.py 加三欄(D4)
套件 update 模式 pkg/jedi-oscal-v2/.../oscal_io_service.py:1308 IR/statement 的 props 整份覆寫;None 跳過、要清空要傳 ""(:356-360) 改逐 key 合併(D5①)
實施狀態值域 三處不一致:FE 下拉 implemented/partial/planned/alternative/not_applicable(ModuleFrameTemplateEditView.vue:348-368 寫死);Excel 匯入 module_frame_template_import_service.py:79-86 為 implemented/partial/not_applicable/not_implemented/inherited/unknown;SQL 註解 scripts/sql/2026-06-02-erd-chinese-comments.sql:720 另一組。OSCAL 標準五值 implemented/partial/planned/alternative/not-applicable 收進 system_menus(D5③、D20)
docx 匯入 另一條路徑 不動(D4)

④ 權限與編輯畫面

接入點 現況 本案動作
資源庫編輯頁 src/views/module_frame/ModuleFrameTemplateEditView.vue(2,246 行、八頁籤;控制項表單 :1555-1610、AO :1760-1810、附件 :724-880),路由 /module-frame/:uid/template-edit 加欄位,新邏輯開子元件(超標檔)
範本 SSP 寫入守門 common/authz/ssp.py:215 _require_admin_for_template:只有帳號層超級管理員能寫,註解留「未來考慮加 mf-owner role」。但雙模式解析器沒有接上(di_containers/oscal/oscal_containers.py:345 註明 ssp_context_resolver 至今未注入),checker 一律走專案分支,這條實際上走不到;資源庫的寫入靠 route 上的能力點與資源庫 service 直接拿 ssp_id 寫 守門要放在真正會經過的入口(D6、D18,見坑 2)
專案 SSP 寫入守門 common/authz/ssp.py:99-103,187-211:專案管理者+可寫判斷 專案側改唯讀(D7)
可寫判斷 domain/oscal/service/ssp_project_resolver.py:111-133:凍結快照一律鎖;living 在無輪次或當前輪 planning 才可寫 規則搬到資源庫側(D9)
SSP 歸屬判斷 實際生效的是 ssp_project_resolver.py:97-108:先查 projects.living_ssp_id、再查 project_audit_rounds.ssp_id,都查不到就「找不到」;雙模式的 ssp_context_resolver.py:98-118(用 profile 反查資源庫)寫好但沒接上 改指向後要重看(見坑 2)
資源庫端點 api/module_frame/__init__.py:96-244,權限 oscal.resource-library.{read,create,update,delete} 沿用 update(D18)
專案規劃頁 src/views/project/ProjectPlanningView.vue(2,609 行),canEditSsp = apStatus === 'planning'(:158);控制項存檔 :1443 沒送 remarks、AO :1463 有送 唯讀(D7)

⑤ 專案啟動、凍結、版本

接入點 現況 本案動作
啟動專案 app/project/service/project_start_app_service.py:347 呼叫套件 clone_resource_library 複製 catalog/profile/SSP 三件組,IR/statement 整列複製;:378 寫 living_ssp_id=new_ssp_id;程序書池與 ssp_risks 手動補複製(:231-299, :392-409) 不再複製,直接指向(D8)
啟動後初始化 :385-389 _init_ssp_control_implementations 對新 SSP 補建控制項 改指向後會作用在範本 SSP 上;卡 8 先盤點再動(D8、坑 3)
專案表 compliance.projects 有 module_frame_id、living_ssp_id(DEV 查證) 一對一約束
一資源庫多專案現況 DEV:資源庫 460 綁 3 個專案、427 綁 1 個;沒有任何專案的 living_ssp_id 等於範本 SSP 不遷移,上版前查 STG/POC(D21)
讀 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 沒有複製端點 把啟動專案現有的複製邏輯搬過來(D8、D17)
稽核凍結 輪次啟動稽核時 snapshot_ssp 複製 living SSP 到 project_audit_rounds.ssp_id;程序書池由 ssp_document_pool_service.py:91 clone_pool_for_snapshot 補 沿用,送審時多存一版並記對應稽核輪(D9)
SSP 主表 v2 只有 id/uuid/metadata_id/status/import_profile_id(DEV 查證),早期 FR-019 的 group_id/version_no 已不存在 不加欄(D10)
OSCAL 版本欄 oscal.metadata 有 version、revisions JSONB,主專案目前沒用 啟用(D10)
整份複製 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 可對快照 dict 比)、decision_merge.py:107 merge_by_decisions 接「SSP 對 SSP」入口(D11)

§5

資料模型草案

props 新鍵(控制項 IR 與 AO statement 都有)

name ns 值 說明
gap https://guidant.ai/ns/oscal 純文字 缺失:少了什麼
recommendation https://guidant.ai/ns/oscal 純文字 缺失修改建議:怎麼補

規則:空字串=刪掉該鍵(與 _setp 現行行為一致);讀取時沒有該鍵視為空。匯出 docx/Excel、差異比對、合併決策都要認得這兩鍵(D5④ 共用 helper 一次處理)。

新表:建議附件 compliance.ssp_control_attachments(主專案)

顧問附在「缺失修改建議」下面的檔案。不分類,只有檔案加一行說明。

欄 型別 用途
id/uid/tenant_id/org_unit_id 標準欄 RLS 依租戶
context_type varchar(32) control_implementation(掛 IR)或 objective(掛 AO statement)
context_id bigint IR.id 或 statement.id
file_id bigint 指向 upload_files
description text 一行說明
審計欄 created_user/created_at/updated_user/updated_at API 回傳時補 *_user_name

程序書池(oscal.ssp_reference_documents)與 jedi-compliance-audit 套件都不動。存版、還原、複製資源庫要把這張表跟著複製並把 context_id 換成新的 IR/statement id(見坑 7);稽核凍結不複製這張表——建議是給客戶改善用的,不是稽核基準(D3)。

OSCAL 原生版本欄(不加欄,只開始寫)

欄 寫法
metadata.version 每存一版寫成版號字串,與索引表 version_label 同步
metadata.revisions 每存一版追加一筆:{version, last-modified, title, remarks, links:[{rel: "predecessor-version", href: "#<前一版 SSP uuid>"}]}

新表:SSP 版本索引 compliance.ssp_versions(主專案)

設計成「SSP 的版本」,不是「資源庫的版本」:第一版只有資源庫用,日後別的擁有者也能掛。

欄 型別 用途
id/uid/tenant_id/org_unit_id 標準欄 RLS 依租戶
owner_type varchar(32) 擁有者種類,第一版只有 module_frame
owner_id bigint 擁有者 id(資源庫 id)
ssp_id bigint 這一版的整份快照 SSP(snapshot_ssp 產出,status=frozen)
version_no int 1、2、3… 同一擁有者遞增
version_label varchar(64) 顯示版號,同步寫 metadata.version
source varchar(32) manual(手動存版)/import(匯入觸發)/submit(送審鎖版)/pre_restore(還原前自動存版)
note text 存版說明
audit_round_id bigint nullable 送審版對應的稽核輪(project_audit_rounds.id);其他來源為空
created_user/created_at 審計欄 API 回傳時補 created_user_name

唯一鍵 UNIQUE (owner_type, owner_id, version_no)。新表照 SQL migration 鐵則加 GRANT ... TO cm_app 與 sequence 權限。

放哪個 schema 的判準:model 寫在哪個套件就放那個 schema。上面兩張 model 都寫在主專案,所以放 compliance;ssp_id 跨 schema FK 指 oscal.system_security_plans 是正常的(先例 project_audit_rounds),FK 要寫完整 schema 名。

稽核凍結快照不進這張表——那份已經記在 project_audit_rounds.ssp_id。送審時存的版(source='submit')與稽核凍結是兩份不同的快照,靠 audit_round_id 互指:前者給顧問回顧比對,後者給稽核輪當基準(坑 10)。

三層關係

層 是什麼 誰改 誰看
工作副本 資源庫的範本 SSP,永遠是最新 顧問(稽核中鎖寫) 顧問;專案規劃頁即時唯讀
版本歷史 某時點的整份快照(ssp_versions 每列一份),建立後不再改 沒人 顧問回顧、比對、還原來源
稽核輪凍結快照 每次送審時凍結給該輪的那份(project_audit_rounds.ssp_id) 沒人 稽核輪;主稽核對第 N 版、覆核對第 M 版,透過 audit_round_id 對得回版本歷史

public.system_menus 實施狀態 seed

主線 phase=seed migration,ON CONFLICT DO NOTHING,先例 scripts/sql/2026-05-22-seed-oscal-ssp-roles.sql。group ssp_implementation_status,key 六個:implemented/partial/planned/alternative/not_applicable/unknown(D20)。FE 改走 API.SYSTEM_MENU,i18n 走 lang.<group>.<key>;Excel 匯入的合法值清單與 BE 驗證改讀同一份。


§6

端到端流程

%%{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)
    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)
    A->>A: 凍結快照給稽核輪
    Note over RL: 稽核中資源庫鎖寫
    A-->>RL: 輪次進整改即解鎖,顧問可繼續整理
    C->>A: 覆核送審(同一套:存版、凍結、鎖寫)
圖 2 — 顧問訪談到稽核(本案新增:存版、送審鎖版、稽核中鎖寫)

儲存與版本操作

操作 做什麼 開版嗎
暫存 全頁未存的控制項與 AO 一次存進工作副本(整條控制項連 AO 批次存 API) 否
儲存成新版本 先暫存,再 snapshot_ssp 複製一份、補程序書池、索引表加一列、metadata.version 加一並追加 revision 是
匯入成功 匯入確認成功後自動存一版 是
看舊版 開舊版 SSP,既有編輯器帶唯讀旗標 —
比對 兩版之間(或某版對工作副本),第一版只比控制項與 AO 的實施層欄位 —
還原到某版 整份換回去,不逐項挑。先列「會改動:控制項 N 條、負責單位 M 筆……」給顧問確認;確定後先把現在的工作副本存成一版(pre_restore),再把那一版整份複製成新工作副本,資源庫與專案的指向一起換過去 是

§7

畫面放法

雛形五頁(假資料,另一棒刻):

頁 檔案 看什麼
索引 prototype/index.html 五頁入口
控制項填寫 prototype/control-edit.html 資源庫「適用控制項」頁籤:控制項與 AO 表單多兩個文字框、建議正下方多「建議附件」區塊(與下方程序書分開)、頁首「暫存」「儲存成新版本」
版本歷史 prototype/version-history.html 版本列表(版號、來源、說明、誰、何時;送審版標對應稽核輪)、看舊版、比對、還原
版本比對 prototype/version-compare.html 兩版之間只列有差異的控制項與 AO,四態著色;整份還原的確認框
專案規劃頁唯讀 prototype/project-planning-readonly.html 控制項欄位全灰、頁首提示「內容即時同步自合規資源庫」、任務設定照舊可編輯

控制項填寫區這次只加欄位(甲案)

  • 控制項表單與 AO 表單各在「備註」之後加「缺失」「缺失修改建議」兩個多行文字框。
  • 「缺失修改建議」正下方加「建議附件」區塊:上傳只選檔、寫一行說明,不分種類。
  • 原本的「程序書」區塊放在建議附件下方,維持原樣、分開呈現。
  • 頁首加「暫存」與「儲存成新版本」兩顆鈕,以及「版本歷史」入口。
  • 其他七個頁籤不動。

理由:先讓顧問能把缺失寫進正確欄位,重新設計填寫區等全部做完、實際用過再看。ModuleFrameTemplateEditView.vue 已 2,246 行,新增的欄位與建議附件區塊一律做成新子元件掛進去。

專案規劃頁:資源庫帶來的資料(控制項與 AO 實施層欄位、程序書、建議附件、系統特性等其他 SSP 頁籤)全部唯讀,頁首一行提示「內容即時同步自合規資源庫」並附「前往合規資源庫」連結(有權限的人才看得到連結);任務設定(指派、到期日、說明)照舊可編,不動。


§8

已裁決策

✅ D1 — 缺失與缺失修改建議存 OSCAL props

定案:新增兩個 props 鍵(gap、recommendation,帶自家 ns),控制項與 AO 兩層都有。不加實體欄、不開側表、不寫 POA&M。

理由:與既有「實施狀態」「現況說明」走同一條路;啟動專案、稽核凍結、存版都是整列複製,props 自動帶走;OSCAL 標準允許自訂 props。

被排除:套件加實體欄(OSCAL 標準沒有這欄,還要動 schema);主專案側表(每條複製路徑都要手動補,程序書池就曾因此漏帶);直接寫評估結果或 POA&M(啟動稽核才會生成,資源庫階段沒有容器可放)。

✅ D2 — 「備註」就是既有 remarks

定案:不新增欄位。

理由:兩表本來就有 remarks;資源庫 AO 有在用,只是專案規劃頁控制項存檔沒送(ProjectPlanningView.vue:1443),改唯讀後這個漏洞自然消失。

✅ D3 — 建議附件獨立一張表,不進程序書池

定案:顧問附在建議下面的檔案存新表 compliance.ssp_control_attachments(context_type 為 control_implementation/objective,加 context_id、file_id→upload_files、description、審計欄)。程序書池不動、不加文件種類欄,jedi-compliance-audit 套件不改。版本存版、還原、複製資源庫時這張表跟著複製;稽核凍結不帶。畫面放在「缺失修改建議」正下方,區塊名「建議附件」,與下方「程序書」分開。不分類,只有檔案加一行說明(D15)。AO 層 props 裡舊的 reference-documents JSON 不碰。

理由:程序書是客戶自己的 SOP,用來證明現況,要進稽核凍結;顧問附件是建議的一部分,客戶照著做完就沒用了,不該混進稽核基準。兩者若同池、靠種類欄過濾,稽核側每個讀程序書的地方都要多一個條件,漏一處就讓建議範本跑進稽核。

被排除:程序書池加「文件種類」欄(稽核側讀取處要逐一加過濾,且要改套件);用檔名或描述欄約定(查不了、篩不了)。

✅ D4 — 訪談表兩條路徑都加三欄,docx 不動

定案:路徑 A(完整匯入)與路徑 B(控制項現況九欄)的欄位定義都加「備註」「缺失」「缺失修改建議」;匯出同步帶出。docx 匯入不動。

理由:訪談表就是現況資料 Excel,顧問在現場直接填;兩條路徑共用欄名定義,只改一處會造成兩份表欄位不一致。docx 是另一種來源,不在顧問訪談流程內。

✅ D5 — 四個坑全收

定案:

  1. 套件 update 模式的 props 改成逐鍵合併:檔案裡有的鍵才蓋,沒有的鍵保留。
  2. 資源庫完整匯入改用合併語意,不清空重建,讓多次匯入可以累積。
  3. 實施狀態值域收進 system_menus 當唯一來源(六值,拼法見 D20),FE、Excel 驗證、BE 驗證都讀它。
  4. props 讀寫 helper 收成一支共用,匯出、差異比對、合併決策同步認得新鍵。

理由:不修①②,顧問第二次匯入只填了缺失欄,上次的現況說明就被清掉——這正是「多次匯入累積」的反面。不修③,三處各自一組值,新欄位上線後下拉、匯入、匯出會各說各話。不修④,新鍵要改六個地方,漏一處就是某條路徑看不到缺失。

✅ D6 — 有資源庫編輯權的人都能編範本 SSP

定案:拿掉「只有帳號層超級管理員能寫範本 SSP」的守門(common/authz/ssp.py:215),改成具備資源庫編輯權限即可。「顧問」角色之後再定義。這條守門目前其實走不到(雙模式解析器沒接上,見盤點 ④),所以實作是「在範本 SSP 的所有寫入入口要求資源庫編輯權」,不是改一行判斷。

理由:顧問是主要編輯者,現行守門是開發期的暫時做法(註解自己寫著「dev-stage gate」)。

被排除:先建「顧問」角色再開權限(角色定義牽涉授權模型,會卡住整案)。

✅ D7 — 專案規劃頁唯讀,編輯權只在資源庫

定案:專案規劃頁上,資源庫帶來的資料除任務設定外全部唯讀。任務設定(指派、到期日、說明)照舊可編、不動;唯讀的是控制項與 AO 實施層欄位、程序書、建議附件、其他 SSP 頁籤。FE 的可編判斷與 BE 專案側 SSP 寫入守門一起改。

理由:客戶專案管理者只指派工作與上傳證據;SSP 是顧問的工作成果,兩邊都能改就會互相覆蓋。

✅ D8 — 資源庫與專案一對一,啟動不再複製 SSP

定案:

  • 啟動專案時專案直接指向資源庫的範本 SSP(living_ssp_id=template_ssp_id),不再複製三件組。
  • 啟動時擋「這個資源庫已經有進行中的專案」。
  • 資源庫有專案綁著不能刪。
  • 新年度走「複製資源庫」產生新資源庫,程序書池、建議附件與表單設定要帶過去,版本歷史不帶。做法見 D17。
  • 資源庫沒有公版。公版是框架層(framework/catalog)的概念;module_frames.scope 欄位保留不拆,概念預留但預設不開放使用者,畫面不出現公版資源庫。顧問從框架層選控制集,自建、自維護資源庫。

卡 8 硬性前置:runner 動手前先逐項盤點 app/project/service/project_start_app_service.py 的複製鏈(三件組、程序書池、ssp_risks、控制項初始化 :385-389),分成三類列出——「啟動專案要留的」「搬去複製資源庫的」「當初順手塞進來的」——回報後才動。這段程式本身寫得亂,不盤點直接改,容易把該留的一起拿掉。

理由:顧問啟動專案後仍持續訪談,只有指向同一份,專案才能即時看到;複製一份就得再設計「資源庫改了怎麼同步到專案」,那是兩份真相。

被排除:維持複製、另做同步(雙向衝突與同步時機都要另外設計);一個資源庫多個專案共用一份 SSP(不同客戶的現況會互相覆蓋);資源庫層加「公版」旗標(公版屬框架層,資源庫層再做一套是兩份概念)。

✅ D9 — 送審即鎖版,稽核中資源庫鎖寫

定案:「送審」就是現在的「啟動稽核」。按下時依序做「存版→鎖版→凍結→鎖寫」:存一版進版本索引(source='submit',audit_round_id 記對應稽核輪),照舊凍結快照給稽核輪,資源庫進入鎖寫。輪次進入整改(remediation)就解鎖(D19)。覆核輪(close_out)送審走同一套,不做特例;覆核送審時再鎖一次。版本歷史與稽核輪靠 audit_round_id 互指,三層關係見資料模型「三層關係」表。稽核流程本身不改。

理由:外部稽核看的是送審那一刻的內容,顧問要能回頭看「送審時寫了什麼」;主稽核與覆核各對應哪一版,要能從兩頭查到。

被排除:稽核中仍允許改工作副本——專案規劃頁直接指向工作副本,客戶專案管理者會看到規劃頁與稽核中的內容不一致;覆核另做一套(兩套鎖版規則容易走歪)。

✅ D10 — 版本走 OSCAL 原生欄位+一張索引表

定案:版號與修訂史寫 OSCAL 原生 metadata.version 與 metadata.revisions(每筆 revision 的 links 帶 rel=predecessor-version 指前一版 uuid);舊版內容用套件 snapshot_ssp 整份複製成獨立 SSP;版本這件事只多一張索引表,設計成「SSP 的版本」(owner_type+owner_id),第一版只有資源庫用。OSCAL 各表不加任何版本欄。

理由:OSCAL 本來就有版本與修訂史欄位,匯出的 SSP 自帶版本資訊;整份 SSP 快照讓看舊版、比對、還原都能直接用既有的編輯器與 diff,不用多一層轉換。

被排除:只寫 metadata(做不到回顧、比對、還原);存 JSON 文件快照(還原要走匯入路徑,匯入的坑沒修之前不可靠;看舊版與比對都要多一層轉換);在 SSP 主表加 group_id/version_no(早期 FR-019 的做法,v2 已移除)。

✅ D11 — 版本操作的範圍

定案:

  • 版本快照是整份 SSP:主表、12 子表、程序書池掛載、建議附件、風險。
  • 暫存不開版;儲存成新版本先暫存再快照;匯入確認成功自動存一版;版本歷史列表;唯讀開舊版(既有編輯器加唯讀旗標)。
  • 比對接既有 diff 的「SSP 對 SSP」入口,第一版只比控制項與 AO 的實施層欄位:狀態、現況、備註、缺失、建議、程序書掛載、建議附件。其他頁籤不比。
  • 還原=整份換回去,不做逐項。按下先列「會改動:控制項 N 條、負責單位 M 筆……」給顧問確認;確定後把舊工作副本自動存成一版(pre_restore),再把那一版複製成新工作副本並換指向,所以永遠可逆。
  • 稽核凍結快照不進索引表。

理由:顧問在乎的是控制項層的內容變化,比對先做這一層,避免範圍膨脹;還原逐項挑會讓「還原後的工作副本」變成哪一版都不是,整份換加上確認清單,顧問知道會動到哪些頁籤。

✅ D12 — 控制項填寫區這次只加欄位

定案:甲案——控制項與 AO 表單各加兩個文字框,建議下方加「建議附件」區塊。重新設計留到全部做完再看。BE 照樣做「整條控制項連 AO 批次存」API,給暫存用。

理由:先讓資料有地方放,畫面改版等顧問實際用過再決定。

✅ D13 — 範本 460 的缺失回填另開小卡

定案:FR-138 在範本 460 塞進「現況說明」的缺失,新欄位上線後開一張小卡,人工或半自動拆到「缺失」欄。

理由:460 是第一個真實使用者案例,拆完正好驗證新欄位與缺失報告的需求。

✅ D14 — props 的 ns 與 name

定案:ns=https://guidant.ai/ns/oscal,name=gap、recommendation。全產品之後的自訂 props 都用同一個 ns。

理由:OSCAL 規定自訂 props 帶自家 ns,避免與標準名稱撞;全產品一個 ns,之後不必再為每個欄位另想。

✅ D15 — 建議附件不分類

定案:建議附件只有檔案加一行說明,不分種類、不進 system_menus。

理由:建議附件與程序書已經分表(D3),種類的作用已由「放在哪張表」承擔,再分類沒有讀取端要用。

✅ D16 — 兩張新表的 schema 與名稱

定案:compliance.ssp_versions(版本索引)、compliance.ssp_control_attachments(建議附件)。判準是「model 寫在哪個套件就放那個 schema」——兩張 model 都在主專案,放 compliance。跨 schema FK 指 oscal.system_security_plans 是正常的,先例 project_audit_rounds。

理由:放 oscal 會讓人以為是 jedi-oscal-v2 套件的表;schema 跟著 model 歸屬走,查表時不必猜誰管。

✅ D17 — 複製資源庫走搬家,不另寫

定案:把啟動專案現有的複製邏輯(套件 clone_resource_library 複製三件組,加程序書池、表單設定、ssp_risks)搬成「複製資源庫」,再補帶建議附件。啟動專案不再用它。不做公版旗標,module_frames.scope 欄位留著(D8)。搬之前照 D8 的卡 8 前置先盤點。

理由:複製路徑與現在的啟動專案幾乎相同,搬過來等於重用;匯出 Excel 再匯入帶不走程序書檔,也依賴匯入坑修完。

被排除:匯出再匯入;資源庫加公版旗標(公版屬框架層,D8)。

✅ D18 — 「有資源庫編輯權」=oscal.resource-library.update

定案:沿用 oscal.resource-library.update(目前 51 處使用,資源庫寫入端點已全用它),不另立新能力點。範本 SSP 的所有寫入入口都要求此能力點,包含坑 2 提到的 SSP 共用端點:這些端點依「此 SSP 是否為某資源庫的 template_ssp_id」分流,是就走資源庫守門(此能力點+稽核中鎖寫),不是才走專案守門。之後定義「顧問」角色時把這個能力點配進去即可。

理由:只守資源庫自己的端點,客戶專案管理者仍能從共用端點改到顧問的 SSP(坑 2、坑 6)。

✅ D19 — 輪次進整改就解鎖

定案:輪次進入整改(remediation)資源庫就解鎖,不必等結案;覆核送審時再鎖(D9)。

理由:顧問在整改期正好要邊訪談、邊更新現況與建議;稽核基準已凍結在稽核輪自己的快照,改工作副本不影響稽核結果。

✅ D20 — 實施狀態保留底線拼法、多收 unknown

定案:key 保留底線 not_applicable,OSCAL 五值之外多收 unknown(=還沒訪談),共六值:implemented/partial/planned/alternative/not_applicable/unknown。值域進 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;底線拼法已散在讀取端與七萬多筆資料裡(ssp_control_implementation_service.py:42 的註解即依賴它),硬改風險大而對使用者沒有可見收益。unknown 是真實狀態,顧問需要。

被排除:key 對齊 OSCAL 連字號並 migration 改既有資料(要找出所有依賴底線拼法的讀取端,unknown 也無處可去)。

✅ D21 — 既有資料不搬,只對新啟動專案生效

定案:既有「一資源庫多專案」的舊模式專案不遷移,維持各自獨立的 SSP 副本,照舊在專案側可寫(守門依「專案 SSP 是否就是某資源庫的 template_ssp_id」分流);一對一檢查只算新模式專案。STG/POC 上線前檢查有無一資源庫多專案,列進上版檢查項。

理由:DEV 實況資源庫 460 綁 3 個專案、427 綁 1 個,全部是舊的「複製一份」模式;遷移要決定哪個專案接手範本 SSP,對既有客戶的稽核輪沒有收益。


§9

拆卡草案(依「決策者何時看得到畫面」排序)

順序 卡 做什麼 主要檔案 前置 驗收(決策者親手做什麼、看到什麼)
1 卡 1 BE 新欄+共用 helper+批次存 API props gap/recommendation 讀寫(ns 照 D14);六份 helper 收一支;整條控制項連 AO 批次存端點;範本 SSP 寫入一律要求 oscal.resource-library.update(兩類入口都要,依 template_ssp_id 分流,見 D18、坑 6) ssp_control_implementation_service.py、資源庫兩支 default service、export loader、diff、decision_merge.py、common/authz/ssp.py — 與卡 2 一起驗
1 卡 2 FE 資源庫控制項欄位+暫存鈕 甲案:兩層表單各加兩框;頁首暫存鈕(接卡 1 批次存) ModuleFrameTemplateEditView.vue 只掛新子元件 卡 1 API 介面先定 在範本 460 填一條控制項與其 AO 的缺失、建議,按暫存後重新整理頁面,內容都還在
1 卡 2b 建議附件 compliance.ssp_control_attachments migration(GRANT 齊全);上傳/列表/改說明/刪除 API(權限同 D18);FE「建議附件」區塊放缺失修改建議正下方,與程序書區分開 新 domain/infra/app service、新 route、FE 新子元件 卡 1 在 460 某條控制項與其 AO 各上傳一份建議附件並寫說明,重新整理還在;下方程序書區與「程序書文件池」頁籤都看不到它
2 卡 3 Excel 三欄+狀態選項+套件合併規則 兩路徑加三欄(匯入匯出、欄名、模板版本);實施狀態 seed(D20 六值)與 FE、Excel 驗證、BE 驗證讀 menu、匯出 OSCAL 轉標準拼法;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:448-510、pkg/jedi-oscal-v2/.../oscal_io_service.py、seed migration 卡 1 下載 460 的訪談表看到三個新欄;只填某條的「缺失」重新匯入,該條現況說明與其他條都沒被清掉;實施狀態下拉選項與 Excel 驗證一致、有「未評估」
3 卡 5 版本索引表+存版 API compliance.ssp_versions migration(含 audit_round_id);存版(暫存→snapshot_ssp→補程序書池與建議附件→索引→metadata 版號與 revision);版本列表;匯入成功自動存版 新 service、ssp_document_pool_service.py:91 複用 卡 1、卡 2b 與卡 6 一起驗
3 卡 6 FE 版本歷史頁+存版鈕 頁首「儲存成新版本」(填說明)、版本歷史頁、唯讀開舊版 新頁+新子元件 卡 5 改兩條控制項後存成新版本,版本歷史多一列且來源寫「手動」;點舊版打開,所有欄位不能改
4 卡 7 比對與還原 既有 diff 加「SSP 對 SSP」入口,只比控制項與 AO 實施層(含程序書掛載、建議附件),其他頁籤不比;整份還原:先算出各頁籤會改動的筆數給確認框,確定後存 pre_restore、複製、換指向 ssp_diff_service.py、還原 service、比對頁 卡 5 比第 1、2 版只看到那兩條的差異標記;按還原到第 1 版,確認框列出「會改動:控制項 2 條……」與新版號;確定後歷史多一列,再還原回去內容一字不差、兩種附件仍在
5 卡 4 範本 460 缺失回填 把塞在現況說明的缺失拆到缺失欄,可半自動(找出含「缺失」字樣的條目列給人確認) DEV 資料作業(只動 DEV) 卡 1、卡 2 在 460 抽五條,現況說明只剩現況,缺失欄有拆出來的那段
6 卡 8 啟動改指向+一對一守門+複製資源庫 前置:先盤點 project_start_app_service.py 複製鏈,分「啟動要留/搬去複製資源庫/順手塞的」三類回報,回報後才動(D8);啟動不再複製、指向範本 SSP;擋重複啟動;有專案不可刪;複製資源庫(搬現有複製邏輯,帶程序書池、建議附件、表單設定、ssp_risks,D17) project_start_app_service.py、資源庫刪除、新複製端點與列表動作 卡 2b 盤點清單先交;從資源庫啟動專案,在資源庫改一條現況,專案規劃頁立刻看到;同一資源庫再啟動一次被擋;複製資源庫後新資源庫內容與兩種附件齊全、版本歷史是空的
7 卡 9 規劃頁唯讀+守門調整 規劃頁資源庫資料唯讀(任務設定照舊可編)、頁首提示;專案側 SSP 寫入守門擋掉「指向範本」的專案;確認 11 支讀 living_ssp_id 的地方無寫入 ProjectPlanningView.vue(只加唯讀旗標與提示子元件)、common/authz/ssp.py、ssp_project_resolver.py(範本判斷改 template_ssp_id 直比,見坑 2) 卡 8 新模式專案的規劃頁,控制項與 AO 欄位全灰、任務指派與到期日仍可改;用專案管理者帳號直接打寫入 API 被擋
8 卡 10 送審鎖版+稽核中鎖寫 主稽核與覆核送審都走「存版(submit+audit_round_id)→鎖版→凍結→鎖寫」;資源庫寫入守門加「綁定專案當前輪在稽核中就鎖」;進整改解鎖(D19) oscal_stage_handlers.py、範本 SSP 寫入守門 卡 5、卡 8 送審後回資源庫,存檔與暫存鈕不能按、版本歷史多一列「送審」並標對應主稽核輪;輪次進整改後又能編輯;覆核送審後再鎖、多一列標覆核輪

e2e 屬母卡收口,不在單卡做。只動一支套件 jedi-oscal-v2(卡 3),開發期走 path dependency,發版等決策者明示;jedi-compliance-audit 不動(D3)。

上版檢查項:STG/POC 上線前查有無「一資源庫多專案」(D21);兩張新表的 migration 照環境異動鐵則只先套 DEV。


§10

不做清單

  • 控制項填寫區重新設計(D12,這次只加欄位)。
  • 其他七個頁籤的任何改動,與版本比對擴到控制項以外的頁籤(D11)。
  • AO 層 props 裡舊的 reference-documents JSON(D3)。
  • docx 匯入加欄位(D4)。
  • 「顧問」角色定義(D6,先用資源庫編輯權)。
  • 稽核中允許改工作副本(D9)。
  • 既有「一資源庫多專案」資料的遷移(D21)。
  • 稽核流程本身的任何改動(D9)。
  • 版本歷史跟著「複製資源庫」帶到新資源庫(D8)。
  • 資源庫層的公版與公版旗標(D8、D17)。
  • 建議附件分類(D15)。
  • 逐項還原(D11)。
  • 缺失報告產出(D13 的 460 回填完成後另議)。

§11

坑與陷阱

🔴 1. 重複匯入會清掉上次的內容

套件 update 模式的 props 是整份覆寫(oscal_io_service.py:1308),資源庫完整匯入是清空重建(ssp_excel_import_app_service.py:448-510)。第二次匯入只填缺失,現況說明就沒了。D5①② 修;修完要拿「只填一欄重匯」實測,不能只看程式。

🔴 2. 改指向後,同一份 SSP 會同時被當成「資源庫的」與「專案的」

現在實際生效的歸屬判斷只有專案那條(ssp_project_resolver.py:97-108:living_ssp_id → 稽核輪 ssp_id);雙模式解析器沒接上,_require_admin_for_template 走不到;資源庫的寫入靠 route 能力點加 service 直接寫。D8 之後:

  • 範本 SSP 的 id 就是專案的 living_ssp_id。所有走 SspPermissionChecker 的 SSP 共用端點(系統特性、元件、外部服務、責任人員、控制項現況等)會把它判成專案 SSP,套用「專案管理者+規劃階段可寫」——客戶專案管理者能透過這些端點改到顧問的 SSP,D7 的唯讀只做在畫面上是擋不住的。
  • 版本快照(索引表那些)既不是 living_ssp_id 也不是稽核輪 ssp_id,走這些端點會「找不到」——唯讀開舊版得另開讀取入口,或讓解析器認得版本快照。
  • 快照沿用來源的 profile(pkg/jedi-oscal-v2/.../ssp/ssp_clone_service.py:129-131 以 replace(src, id=None, …) 整列複製根節點),若日後把雙模式解析器接上,快照會被 profile 反查判成範本、變成可寫。

判斷要改成:範本=module_frames.template_ssp_id 直接比對(不靠 profile),命中就走資源庫守門(能力點+稽核中鎖寫),不再走專案管理者那條;版本索引裡的 SSP 與 status='frozen' 一律唯讀。能力點分流在卡 1(D18),歸屬判斷與專案側擋法在卡 9,稽核中鎖寫在卡 10。

🔴 3. 啟動專案後的初始化會作用在範本 SSP 上

project_start_app_service.py:385-389 的 _init_ssp_control_implementations 是對「剛複製出來的新 SSP」補建控制項。改指向後它會直接寫範本 SSP;程序書池與 ssp_risks 的補複製(:392-409)也會變成自己複製給自己。所以卡 8 的盤點前置是硬性的(D8)。

4. props 讀寫 helper 有六份

新鍵要在六處都認得;_setp 遇空字串刪鍵、_map_to_props 全空回 None,共用版要保留這兩個行為,否則「清空某欄」會變成存一個空字串鍵。export loader、差異比對、合併決策漏改,那條路徑就看不到缺失,且不會報錯。

5. 同一控制項可能有多筆 IR

_ir_for_control(service :888-900)挑「已有說明」那筆。新欄位讀寫、批次存、比對都要走同一個挑選,否則缺失寫進 A 筆、畫面讀 B 筆。

6. 守門要兩條一起改

範本 SSP 的寫入入口有兩類:資源庫自己的端點(route 能力點+service 直接寫)與 SSP 共用端點(經 SspPermissionChecker)。「有資源庫編輯權才能寫」與「稽核中鎖寫」兩條規則要在兩類入口都成立。只改一類,另一類就是漏洞。卡 1 先處理能力點、卡 10 補稽核中鎖寫,中間這段時間範本 SSP 沒有稽核中鎖——卡 8 上線前不會有新模式專案,風險可接受。

7. 存版與還原時 IR 會重產 id,程序書掛載要跟著複製

程序書掛載表與建議附件表都用 IR.id 與 statement.id,snapshot_ssp 會重產 id。存版、還原、複製資源庫都要複用 clone_pool_for_snapshot(ssp_document_pool_service.py:91),建議附件另外照同樣的 id 對照表複製;稽核凍結只帶程序書、不帶建議附件。任務與控制項的對應(workflow_execution_control_mapping)是用 control_id 字串+輪次,不受 IR id 變動影響。

8. 還原會換掉範本 SSP 的 id

還原=換一份新工作副本,template_ssp_id 與一對一專案的 living_ssp_id 要同時換。11 支讀 living_ssp_id 的地方照常讀即可,但有快取或背景工作記住舊 id 的要查(例如 task_alert 兩支)。

9. 不可為空的欄位與資源庫 service 的權限繞道

by_component.description 等欄位不可為空,存版與還原的複製路徑要沿用套件既有處理。資源庫的 default service 直接拿 ssp_id 寫,不經 SspPermissionChecker(見坑 2、6)。

10. 送審時會產生兩份快照

送審存一版(submit,進索引,帶 audit_round_id)+稽核凍結(進 project_audit_rounds.ssp_id),各一份;差別在稽核凍結不帶建議附件。第一版接受兩份,求兩邊各自單純,靠 audit_round_id 互指。

11. 前端兩支超標檔

ModuleFrameTemplateEditView.vue 2,246 行、ProjectPlanningView.vue 2,609 行,都超過 800 行上限。新增邏輯一律新開子元件,原檔只加一行掛載;ddd-compliance-reviewer 遇到往裡塞會標阻斷。