FR-139 · 討論稿 · 2026-10-14
D1~D21 全部已裁;三個子需求、十一張卡;附四頁假資料雛形
這份把決策者 2026-10-14 裁定的 D1~D21 展開成可以開工的規格:控制項與評估目標多「缺失」「缺失修改建議」兩欄、建議下方多一塊「建議附件」(獨立一張表,不進程序書池)、訪談表重複匯入可以累積、資源庫有版本可存可比可整份還原、資源庫與專案改一對一且專案規劃頁唯讀、每次送審(主稽核與覆核)都鎖一版。每一項都附現況座標(程式碼行號或 DEV 唯讀查詢)。請先開 雛形索引 點五頁,再回來看規格。
設計定案拍板後寫 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 物件上的「名稱+值」擴充屬性陣列。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-14 | 初稿:已裁 D1~D13 展開、待補裁 P1~P8;資料模型草案、端到端流程、十卡拆卡草案、四頁雛形引用 | — |
| 2026-10-14 | 決策者審雛形與 P1~P8 後定案 | D3、D7~D11、D14~D21、卡 2b |
整案一句話:顧問在合規資源庫裡把訪談結果寫完整(現況、備註、缺失、修改建議、附範本),每整理一輪就存一版;顧問覺得可以了就送審,送審那刻鎖一版給外部稽核用。專案只看資源庫那一份,不能改。
| 子需求 | 做什麼 | 產出 | 完成怎麼判定(決策者親手做什麼、看到什麼) |
|---|---|---|---|
| 一、訪談欄位與匯入 | 控制項與 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
合規資源庫是顧問替客戶導入法規框架的第一個入口。顧問先到客戶現場訪談,手上拿的訪談表就是系統可以下載的「現況資料 Excel」;回來把訪談表匯入系統,同一份表會隨著訪談次數增加反覆匯入。之後顧問逐條整理控制項與 AO 的「實施狀態、現況說明、備註」。
整理到「部分實作」這類條目時,顧問要寫出缺了什麼與怎麼補,手上若有可以直接給客戶用的範本就附上。這一步其實就是正式稽核之前的事前稽核。以 CRA 的漏洞通報條為例:
| 欄位 | 顧問寫的內容 |
|---|---|
| 實施狀態 | 部分實作 |
| 現況說明 | 只有提供 E-Mail 收信接收通報 |
| 備註 | 連 email 都未開通 |
| 缺失(新) | 無通報平台、也沒有公告、也沒有通報手冊 |
| 缺失修改建議(新) | 提供通報平台網址、官網公告通報方式、提供手冊下載 |
| 建議附件(新) | 通報流程手冊範本.pdf(說明:照這份補上收件、分級、修補、回覆四段) |
FR-138 自家 CRA 自評時,缺失沒有地方放,只能塞進「現況說明」:一個欄位同時講「現在是什麼」和「少了什麼」,要產缺失報告得人工再拆一次。同一份訪談表重新匯入,還可能把上次填好的欄位清掉(見盤點 ② 節)。
| 接入點 | 現況 | 本案動作 |
|---|---|---|
| 實施狀態 | 存 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) |
| 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)。
| 欄 | 寫法 |
|---|---|
metadata.version |
每存一版寫成版號字串,與索引表 version_label 同步 |
metadata.revisions |
每存一版追加一筆:{version, last-modified, title, remarks, links:[{rel: "predecessor-version", href: "#<前一版 SSP uuid>"}]} |
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 驗證改讀同一份。
%%{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: 覆核送審(同一套:存版、凍結、鎖寫)
| 操作 | 做什麼 | 開版嗎 |
|---|---|---|
| 暫存 | 全頁未存的控制項與 AO 一次存進工作副本(整條控制項連 AO 批次存 API) | 否 |
| 儲存成新版本 | 先暫存,再 snapshot_ssp 複製一份、補程序書池、索引表加一列、metadata.version 加一並追加 revision |
是 |
| 匯入成功 | 匯入確認成功後自動存一版 | 是 |
| 看舊版 | 開舊版 SSP,既有編輯器帶唯讀旗標 | — |
| 比對 | 兩版之間(或某版對工作副本),第一版只比控制項與 AO 的實施層欄位 | — |
| 還原到某版 | 整份換回去,不逐項挑。先列「會改動:控制項 N 條、負責單位 M 筆……」給顧問確認;確定後先把現在的工作副本存成一版(pre_restore),再把那一版整份複製成新工作副本,資源庫與專案的指向一起換過去 |
是 |
雛形五頁(假資料,另一棒刻):
| 頁 | 檔案 | 看什麼 |
|---|---|---|
| 索引 | prototype/index.html | 五頁入口 |
| 控制項填寫 | prototype/control-edit.html | 資源庫「適用控制項」頁籤:控制項與 AO 表單多兩個文字框、建議正下方多「建議附件」區塊(與下方程序書分開)、頁首「暫存」「儲存成新版本」 |
| 版本歷史 | prototype/version-history.html | 版本列表(版號、來源、說明、誰、何時;送審版標對應稽核輪)、看舊版、比對、還原 |
| 版本比對 | prototype/version-compare.html | 兩版之間只列有差異的控制項與 AO,四態著色;整份還原的確認框 |
| 專案規劃頁唯讀 | prototype/project-planning-readonly.html | 控制項欄位全灰、頁首提示「內容即時同步自合規資源庫」、任務設定照舊可編輯 |
控制項填寫區這次只加欄位(甲案)
理由:先讓顧問能把缺失寫進正確欄位,重新設計填寫區等全部做完、實際用過再看。ModuleFrameTemplateEditView.vue 已 2,246 行,新增的欄位與建議附件區塊一律做成新子元件掛進去。
專案規劃頁:資源庫帶來的資料(控制項與 AO 實施層欄位、程序書、建議附件、系統特性等其他 SSP 頁籤)全部唯讀,頁首一行提示「內容即時同步自合規資源庫」並附「前往合規資源庫」連結(有權限的人才看得到連結);任務設定(指派、到期日、說明)照舊可編,不動。
✅ 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 — 四個坑全收
定案:
system_menus 當唯一來源(六值,拼法見 D20),FE、Excel 驗證、BE 驗證都讀它。理由:不修①②,顧問第二次匯入只填了缺失欄,上次的現況說明就被清掉——這正是「多次匯入累積」的反面。不修③,三處各自一組值,新欄位上線後下拉、匯入、匯出會各說各話。不修④,新鍵要改六個地方,漏一處就是某條路徑看不到缺失。
✅ D6 — 有資源庫編輯權的人都能編範本 SSP
定案:拿掉「只有帳號層超級管理員能寫範本 SSP」的守門(common/authz/ssp.py:215),改成具備資源庫編輯權限即可。「顧問」角色之後再定義。這條守門目前其實走不到(雙模式解析器沒接上,見盤點 ④),所以實作是「在範本 SSP 的所有寫入入口要求資源庫編輯權」,不是改一行判斷。
理由:顧問是主要編輯者,現行守門是開發期的暫時做法(註解自己寫著「dev-stage gate」)。
被排除:先建「顧問」角色再開權限(角色定義牽涉授權模型,會卡住整案)。
✅ D7 — 專案規劃頁唯讀,編輯權只在資源庫
定案:專案規劃頁上,資源庫帶來的資料除任務設定外全部唯讀。任務設定(指派、到期日、說明)照舊可編、不動;唯讀的是控制項與 AO 實施層欄位、程序書、建議附件、其他 SSP 頁籤。FE 的可編判斷與 BE 專案側 SSP 寫入守門一起改。
理由:客戶專案管理者只指派工作與上傳證據;SSP 是顧問的工作成果,兩邊都能改就會互相覆蓋。
✅ D8 — 資源庫與專案一對一,啟動不再複製 SSP
定案:
living_ssp_id=template_ssp_id),不再複製三件組。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 — 版本操作的範圍
定案:
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,對既有客戶的稽核輪沒有收益。
| 順序 | 卡 | 做什麼 | 主要檔案 | 前置 | 驗收(決策者親手做什麼、看到什麼) |
|---|---|---|---|---|---|
| 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。
reference-documents JSON(D3)。🔴 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 之後:
living_ssp_id。所有走 SspPermissionChecker 的 SSP 共用端點(系統特性、元件、外部服務、責任人員、控制項現況等)會把它判成專案 SSP,套用「專案管理者+規劃階段可寫」——客戶專案管理者能透過這些端點改到顧問的 SSP,D7 的唯讀只做在畫面上是擋不住的。living_ssp_id 也不是稽核輪 ssp_id,走這些端點會「找不到」——唯讀開舊版得另開讀取入口,或讓解析器認得版本快照。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 遇到往裡塞會標阻斷。