FR-147 · 設計定案 v1 · 2026-10-17

資源庫任務說明:設計定案

顧問在資源庫替每條評估目標的預設任務寫名稱、描述、指南;啟動帶進任務;一份 Excel 兩邊共用

合規資源庫的「控制項與評估目標」頁,每條評估目標(AO)底下多一段「任務說明」。專案還沒啟動時每條 AO 固定一個範本預設任務,顧問填它的名稱、描述、指南,三欄存在範本流程圖的方塊上;專案啟動時原封帶進任務;啟動後這一段改讀專案目前輪次的任務,PM 改過流程就看到多個,顧問改的三欄直接寫進任務。顧問不改流程,資源庫頁的流程編輯器入口收掉。一份 Excel 一列一任務、AO 欄合併儲存格,資源庫頁與專案規劃頁同一份格式。不開新表、不動 jedi 套件、不套 migration。本文件寫定案規則、為什麼這樣定、踩到會怎樣。

D1~D4、D6 定案(2026-10-17) 三棒八張卡(v2 加 3c 版型),可平行 不開新表、不動套件、不套 migration 陷阱:圖的 XML 是可翻譯欄位要雙寫

🔴 這份是設計決策,不是現況。 內文停在 2026-10-17 定稿當時,實作改動不回頭改寫這裡。

狀態:設計定案,待開卡派工|討論稿:discussion.md|雛形:prototype/index.html

檔案路徑與行號以 2026-10-17 為準,查不到時以函式名重新定位。路徑慣例:BE 路徑相對 compliance-manager-be/;FE 路徑相對 compliance-manager-fe/;pkg/<套件>/ 代表 ~/Projects/Jedicogy/module/jedi-python-package/<套件>/。

名詞:資源庫=合規資源庫(compliance.module_frames),一本只給一個專案用;AO=評估目標,每條控制項底下的檢查項目,鍵是 catalog_control_parts.part_id;範本流程圖=每條 AO 配的一張 BPMN 圖(compliance.workflow_templates,uid 寫在 AO props 的 workflow-template-uid),預設一個方塊;範本預設任務=那個方塊,資源庫階段「任務」指的就是它;任務=compliance.job_executions 一列,有 name/description/guide;目前輪次=專案最新一輪(第 1 輪或最新覆核輪)。

§1

變更紀錄

日期 變更 對應
2026-10-17 v1:D1~D4、D6 定案;資料落點、讀寫規則、Excel 契約、三棒七張卡、端到端驗收 —
2026-10-17 v2:決策者審雛形 v3 後加 D7 資源庫編輯頁版型比照稽核計畫頁(左樹到 AO、右欄一次一條),新增卡 3c、3b 改依賴 3c;雛形 v4 CM-2872

§2

分工概述(30 秒版)

整案一句話:資源庫頁每條 AO 底下多一段「任務說明」,顧問填預設任務的名稱、描述、指南;啟動帶進任務、啟動後直接改任務;一份 Excel 兩邊共用;顧問不改流程。

卡 做什麼 產出 完成怎麼判定(決策者親手做什麼、看到什麼)
2a 專案啟動時三欄從範本方塊帶進任務 方塊屬性讀寫 helper;任務產生改三欄來源 DEV 把一本資源庫某條 AO 三欄用 API 填好,用它啟動專案,規劃頁那條 AO 的任務名稱、描述、指南就是填的;沒填的資源庫啟動後任務名稱仍是「控制碼 / AO 收證據」
2b 覆核輪新任務抄上一輪三欄 覆核輪承襲加三欄 DEV 在第 1 輪任務填三欄後啟動覆核輪,新輪同一條 AO 的任務三欄一樣
3a 資源庫 AO 任務說明讀寫 API 新 service、route、readmodel 沒綁專案的資源庫:讀回一個任務、寫三欄再讀回一致;綁了專案的:讀回專案目前輪次任務、寫三欄後規劃頁任務也變
3c 資源庫編輯頁版型比照稽核計畫頁(D7) 左樹到 AO、右欄單一 AO 表單 對照雛形 v4:左樹點控制項右邊只有控制項欄位,點 AO 右邊只有那條 AO;上一條/下一條;15 條 AO 的控制項不再整頁堆
3b 右欄 AO 表單底下加「任務說明」區塊、左樹圓點、收掉流程編輯器入口 FE 新子元件掛進 AO 表單、拿掉一顆鈕 對照雛形畫面 1:AO 展開看到任務小卡三格、填存重整還在;已啟動的資源庫看到專案任務(多個時有流程帶、狀態與指派人唯讀);AO 右邊沒有流程編輯器鈕
4a 任務說明 Excel 匯出匯入(資源庫端) 共用 workbook builder、解析、預覽、確認 資源庫頁匯出開 Excel:一列一任務、多任務的 AO 前幾欄合併、三欄黃底;改兩格匯回預覽只標那兩格、確認後畫面更新
4b 專案規劃頁既有任務配置匯出匯入對齊 加描述指南兩欄、AO 合併、匯入收三欄 專案規劃頁匯出長相與資源庫一致(多指派、部門、設備欄);改三欄匯回寫進任務
4c 資源庫頁匯出匯入對話框與預覽 FE 對照雛形匯入預覽

依賴:2a 先建 helper,3a 的「寫圖」用同一支;3c(版型)不依賴後端可與 2a 同時派;3b 等 3a 與 3c;其餘可平行。

%%{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 B2[棒 2 啟動與覆核輪]
      A2a[2a helper+啟動帶三欄] --> A2b[2b 覆核輪抄三欄]
    end
    subgraph B3[棒 3 資源庫頁]
      A3a[3a 讀寫 API] --> A3b[3b FE 區塊+收入口]
    end
    subgraph B4[棒 4 Excel]
      A4a[4a 資源庫端匯出匯入] --> A4c[4c FE 對話框]
      A4a --> A4b[4b 專案端對齊]
    end
    A2a -.方塊屬性 helper.-> A3a
    A3a -.讀取 readmodel.-> A4a
圖 1 — 七張卡的依賴(箭頭=必須先做)

§3

決策定案

編號 定案 理由 被排除
D1 啟動前三欄存範本流程圖的方塊上:名稱=方塊 name,描述與指南=方塊的 camunda 屬性 task_description/task_guide 與既有掛任務編號(job_execution_uid)同一招;不開新表、不套 migration;複製資源庫連圖一起 clone 免費帶走;沒有第二份真相 另開「AO 任務說明」表:顧問刪方塊時表上殘留、複製要多抄、要 RLS 與 migration
D2 啟動後顧問在資源庫頁改三欄,守門=資源庫編輯權(能力點 oscal.resource-library.update + verify_module_frame_exists_by_uid(writable=True) 歸屬守門 + 稽核鎖 assert_template_not_audit_locked),不要求專案 manager;端點只開三欄。已綁專案時這三欄直接寫進專案目前輪次的任務,非專案成員的顧問也能改——這是本意,決策者 2026-10-22 再次確認(arc-review 重要級 3 曾列為疑慮) 決策者裁「顧問只改三欄文字,流程歸 PM」;顧問通常不是專案成員;三欄是說明文字不是稽核範圖 要求同時是專案 manager:顧問根本進不來
D3 啟動後資源庫頁讀專案目前輪次的任務(wecm 按 round_id,與任務配置匯出同一條查法) 覆核輪啟動後顧問改的應是新輪;舊輪已進稽核不該再動 固定第 1 輪
D4 Excel 匯入對不到的列跳過、其餘照寫,預覽列出跳過數 與既有任務配置匯入一致;PM 改過任務後整份退回會永遠匯不進去 一列對不到整份退回
D6 資源庫頁每條 AO 的「流程編輯器」按鈕拿掉;資源庫階段每條 AO 固定一個範本預設任務,顧問只填三欄;流程一律啟動後 PM 在專案規劃頁改 決策者裁「不想讓顧問有這麼大權限」;現況那顆鈕設了也白設(啟動只認第一個方塊);拿掉後資源庫永遠一 AO 一任務,畫面、Excel、啟動邏輯都不必處理多方塊 留著鈕並把啟動改成整張圖都長任務:權限方向相反,且動啟動與覆核輪共用核心
D7 資源庫編輯頁「適用控制項」頁籤版型改成與稽核計畫頁(FR-141 ApObjectiveTree+ApObjectiveAoForm)相同:左樹群組 → 控制項 → AO(每條 AO 一節點,圓點標任務說明填了沒),右欄一次只顯示一個:點控制項顯示控制項欄位、底下列這條控制項的 AO 清單可點跳過去(圓點、編號、文字、任務數與有說明數),點 AO 顯示那條 AO 的全部欄位+任務說明;上一條/下一條。兩個匯入預覽不動;專案規劃頁另案 決策者原話「有的 AO 一多這邊就爆掉了」「比照稽核計畫、稽核作業那樣」;現行頁右欄把一條控制項的全部 AO(DEV 最多 15 條)直向堆,加任務說明後更長 右欄上段固定控制項欄位、下段 AO(兩段都長);三欄(多一欄,與稽核計畫頁不一致)
D5 撤銷 PM 在專案規劃頁改方塊名稱與任務列名稱的關係是現況既有行為(兩邊各自、不同步),與本案無關 —

§4

現況接入點

接入點 現況 本案動作 卡
建資源庫初始化 AO 流程圖 app/oscal/service/resource_library_app_service.py:_init_ao_workflows()(L269) 每條 AO 一張圖、一個方塊「Evidence upload」,uid 寫進 AO props 不動;那個方塊就是範本預設任務 —
專案啟動長任務 app/flow_control/service/prep_job_generation_service.py:generate()(L122) 只認第一個 userTask;label = f"{cid} / {ao_part_id} 收證據"(L241)寫死名稱;描述指南不帶 名稱改取方塊 name(空則退回原 label);描述指南取方塊屬性 2a
覆核輪長任務 同一支 reclone_from_props=True 同上 名稱、描述、指南從上一輪同 AO 任務列抄;配對用 reverify_inheritance_service 的 get_job_pairs 2b
覆核輪承襲 app/flow_control/service/reverify_inheritance_service.py:inherit_from_parent()(L40) 抄指派、部門、設備、證據、問卷,不抄三欄 配對結果多抄三欄 2b
編輯器存檔三向比對 app/flow_control/service/workflow_xml_sync_service.py:sync()(L64) 圖上沒編號的方塊建任務、沒在圖上的任務刪 不動;資源庫頁的寫圖不經它(只改屬性不增刪方塊) —
資源庫一對一 infra/readmodel/oscal/resource_library_binding_query.py(ever_bound/has_live_project) 已有 3a 用它判「讀圖還是讀任務」;要再補一支「資源庫 → 專案 id 與目前輪次 id」 3a
資源庫寫入守門 domain/module_frame/service/module_frame_domain_service.py:verify_module_frame_exists_by_uid(writable=True)(L79)、assert_template_not_audit_locked(L98) 已有 3a 兩支寫入端點照走 3a
任務三欄更新 app/flow_control/service/job_service.py:update_job()(L331) 收三欄,守專案 manager 不動;3a 啟動後寫三欄走 domain service update_job 只帶三欄、守門換 D2 3a
圖 XML 雙寫 infra/flow_control/repository/flow_control_job_repo_impl.py:write_template_xml() 主表與翻譯列一起寫 3a 寫圖用它 3a
FE 資源庫頁 src/views/module_frame/ModuleFrameTemplateEditView.vue(1,954 行) AO 展開區 L1496~;流程編輯器鈕 L1479~1487 新區塊開子元件,原檔只加一行掛載、刪那顆鈕 3b
FE 任務表單 src/components/grc/planning/AoTaskForm.vue(L395~420) 三格已存在 3b 照這三格的樣子刻 3b
任務配置匯出匯入 app/flow_control/service/job_import_service.py(546 行) 十欄、一任務一列、AO 欄不合併、匯入不收三欄 4b 加兩欄、合併、收三欄;加欄時把表頭常數與列組裝抽到共用 builder,不往裡塞 4a、4b
匯出查詢 infra/readmodel/tasks/job_export_query.py:get_export_data()(L85) 回 job_name,沒 description/guide 加兩欄 4a
改善計畫匯出 app/flow_control/service/poam_workbook_export.py:_plan_sheet()(L131~205) AO 多列合併、列高平分、灰黃著色 合併與著色寫法借用 4a
複製資源庫 app/module_frame/service/resource_library_copy_service.py:_copy_ao_workflows()(L102) 連圖 clone 不動,三欄在圖上自動帶走 —

§5

詳細設計

資料落點

  • 啟動前:三欄在範本流程圖第一個 userTask 方塊上。name 是方塊的 name 屬性;描述與指南是 <camunda:properties> 底下兩個 <camunda:property name="task_description" value="…"/>、name="task_guide"。
  • 啟動後:三欄在任務列 job_executions.name/description/guide。
  • 兩個落點之間只有一條單向流:啟動時從圖帶進任務列。啟動後改任務列不回寫圖。

方塊屬性 helper(2a 建,3a 共用)

新檔 app/flow_control/service/task_node_props.py,純函式、不碰 DB:

  • read_first_task(xml) -> {node_id, name, description, guide} | None:用 BpmnUtils(xml).get_jobs() 取第一個 userTask(與 _first_user_task_id 同一個「第一個」),從 properties 取兩個屬性。
  • write_first_task(xml, name, description, guide) -> xml:只改第一個 userTask 的 name 與那兩個屬性;不動方塊 id、線、其他屬性(含 job_execution_uid);屬性值為空就把該屬性拿掉,<camunda:properties> 清到空要整段拿掉(空節點會讓 BpmnUtils 解析炸,見 flow_apply_to_tasks_service.py:_strip_uid 註解)。
  • 實作手法照 flow_apply_to_tasks_service.py 用 xmltodict parse/unparse。

專案啟動帶三欄(2a)

prep_job_generation_service.py:generate() 專案啟動模式(reclone_from_props=False):clone 完範本後 read_first_task(cloned.xml),name 有值就用它當 label,否則維持 f"{cid} / {ao_part_id} 收證據";description/guide 直接寫進 JobExecutionEntity。workflow_execution 的 name 維持原 label(它是流程實例名稱,不是任務名稱)。

覆核輪抄三欄(2b)

generate() 覆核輪模式不讀圖(上一輪的圖方塊名稱是上一輪 clone 當下的,PM 之後改的任務名稱不在圖上)。改在 reverify_inheritance_service.py:inherit_from_parent() 的 get_job_pairs 迴圈裡,每對新舊任務把舊任務的三欄 UPDATE 到新任務(infra 層加一支 clone_task_text_fields(pair),與 clone_assignees 同形)。

資源庫 AO 任務說明 API(3a)

新 app/module_frame/service/ao_task_guidance_service.py(@transaction,守門見 D2):

端點 做什麼
GET /module-frame/<uid>/control/<control_code>/task-guidance 回這條控制項所有 AO 的任務清單:`{source: "template"
PUT /module-frame/<uid>/task-guidance body [{key, name, description, guide}],批次寫。template 模式:解析 key 找 AO 的圖 uid,write_first_task 後走 write_template_xml 雙寫;project 模式:走 flow_control domain update_job 只帶三欄,不經 job_service.update_job(那支守專案 manager)

左樹計數(已填說明/任務數)由同一支 GET 的聚合版 GET /module-frame/<uid>/task-guidance/summary 回每條控制項 {code, filled, total},一次 SQL/一次解析,不逐條打。

新 readmodel infra/readmodel/module_frame/ao_task_guidance_query.py:綁定資源庫 → 專案 id、目前輪次 id;按輪次撈 AO 任務(與 job_export_query.py 同一條 wecm 查法)。

資源庫頁區塊(3b)

新子元件 src/components/grc/module-frame/AoTaskGuidanceSection.vue,掛在 ModuleFrameTemplateEditView.vue AO 展開區「參考程序書」之後,一行 <AoTaskGuidanceSection :module-frame-uid :control-code :readonly="isVersionView || writeDisabled()" />。元件自己打 GET、三格 @change 累積、頁面儲存時 emit 批次 PUT。版本檢視(isVersionView)唯讀顯示三欄文字。

長相照雛形畫面 1:template 一張小卡;project 多張小卡+上方流程帶,小卡右上角狀態徽章與指派人(唯讀)。刪掉 L1479~1487 的流程編輯器 <button> 與 openBpmnEditor(D6)。

Excel 契約(4a、4b)

共用 builder app/flow_control/service/task_guidance_workbook.py:

欄 內容 匯入
A(隱藏) 比對鍵:template 模式 `<part_id> <node_id>`,project 模式 job_uid
B 控制項群組、C 控制項識別碼、D 控制項名稱、E 評估目標 灰底唯讀;同一 AO 多列時 B~E merge_cells 不讀
F 順序 流程順序 1..n 不讀
G 任務名稱、H 描述、I 指南 黃底可改 讀、只寫這三欄
(專案端加)J 任務類型、K 指派人員、L 參與部門、M 設備 既有欄位保留 既有邏輯

另一頁「填寫說明」(照 prototype/sample/make_sample_xlsx.py 的六條)。合併、列高、著色借 poam_workbook_export.py:_plan_sheet() 的寫法。

匯入三段:parse(回每列的新舊值與變更旗標、跳過清單)→ FE 預覽只標變更格 → confirm(只寫有變更且 key 對得到的列;D4 跳過不報錯)。資源庫端端點 POST /module-frame/<uid>/task-guidance/export、.../import/parse、.../import/confirm;守門同 3a。

專案端 job_import_service.py 改用共用 builder 產前九欄,_EXPORT_COLUMNS 加 description/guide,confirm_import 多把三欄傳進 update_job。job_export_query.py 回 description/guide。

權限與鎖

  • 資源庫端三支寫入(PUT、import/confirm):@require_capability("oscal.resource-library.update") + service 內 verify_module_frame_exists_by_uid(uid, writable=True) + assert_template_not_audit_locked。
  • 資源庫端讀取:資源庫讀權(既有)。
  • 專案端匯出匯入:維持既有專案 manager 守門。
  • 稽核中(FR-139 鎖)資源庫頁三欄唯讀,與既有欄位一致。

§6

端到端驗收

  1. DEV 建一本新資源庫(或用沒綁專案的既有庫),資源庫頁某控制項展開 AO,看到一張「任務說明」小卡、名稱「Evidence upload」、描述指南空白;AO 右邊沒有流程編輯器鈕。
  2. 填三欄存檔、重整還在;另一條 AO 不受影響。左樹該控制項計數變 1/N。
  3. 匯出任務說明 Excel,開檔:每 AO 一列、三欄黃底、第一列是剛填的;改另一條 AO 兩格匯回,預覽只標兩格、確認後畫面更新。
  4. 用這本資源庫啟動專案:規劃頁該 AO 的任務名稱、描述、指南=資源庫填的;沒填的 AO 任務名稱仍是「控制碼 / AO 收證據」。
  5. PM 在專案規劃頁把一條 AO 流程改成三站;回資源庫頁同一條 AO 看到三張小卡、流程帶三格、狀態與指派人唯讀;改第二張的指南存檔,規劃頁該任務指南同步。
  6. 資源庫匯出 Excel:那條 AO 三列、B~E 合併;專案規劃頁任務配置匯出長相一致且多指派、部門、設備欄。
  7. 啟動覆核輪:新輪同一條 AO 的任務三欄=上一輪。
  8. 覆核輪送審鎖寫後資源庫頁三欄唯讀。

§7

拆分表

卡 做什麼 碰哪裡 依賴 驗收
2a 方塊屬性 helper+專案啟動帶三欄 task_node_props.py 新檔;prep_job_generation_service.py L229~252 — 驗收 4
2b 覆核輪抄三欄 reverify_inheritance_service.py、reverify_clone_query 加 clone_task_text_fields 2a 驗收 7
3a 任務說明讀寫 API+summary+readmodel ao_task_guidance_service.py 新、route、ao_task_guidance_query.py 新 2a helper 驗收 1、2、5 的 API 面
3c 版型比照稽核計畫頁:左樹到 AO、右欄單一控制項或 AO 表單(D7) 新 MfControlTree.vue、MfControlForm.vue、MfAoForm.vue(從 ModuleFrameTemplateEditView.vue L1173~1560 抽出);原檔變薄 — 左樹點控制項/AO 右欄各只顯示一個;既有欄位與存檔行為不變
3b AO 表單底下任務說明子元件、左樹圓點、收流程編輯器入口 AoTaskGuidanceSection.vue 新,掛進 MfAoForm.vue 3a、3c 驗收 1、2、5、8
4a Excel 共用 builder、資源庫端匯出/parse/confirm task_guidance_workbook.py 新、route、job_export_query.py 加兩欄 3a readmodel 驗收 3、6 前半
4b 專案端任務配置匯出匯入對齊 job_import_service.py(抽共用、不往裡塞) 4a 驗收 6 後半
4c FE 匯出匯入對話框與預覽 FE 3b、4a 驗收 3

§8

坑與陷阱

坑 在哪 怎麼避
圖的 XML 是可翻譯欄位,只寫主表編輯器看不到 prep_job_generation_service.py L262 註解 寫圖一律 write_template_xml 雙寫
資源庫頁寫方塊屬性時動到 job_execution_uid 或方塊 id 三向比對 workflow_xml_sync_service.py L94 會判成新任務或查不到 helper 只改 name 與兩個自家屬性,其餘原樣
空的 <camunda:properties/> 讓解析炸 flow_apply_to_tasks_service.py:_strip_uid 清到空整段拿掉
啟動後走 job_service.update_job 會擋顧問 它守專案 manager 3a 走 domain update_job 只帶三欄,守門換 D2
資源庫側行為要用資源庫入口驗 FR-139 T-24 教訓 驗收用 /module-frame/... 端點,不拿專案端點頂
ModuleFrameTemplateEditView.vue 超標 1,954 行 新區塊必開子元件,原檔只加一行、刪一顆鈕
job_import_service.py 超標邊緣 546 行 4b 把表頭與列組裝抽到共用 builder,淨行數不增
改 service 後共用 BE 跑舊程式 2026-10-15 教訓 驗收前先重啟