FR-141 · 設計定案 v2 · 2026-10-16

稽核計畫重做:設計定案

計畫概要與評估目標兩個頁籤、一份 Excel 匯出即匯入範本、亞航格式報告稿匯出

稽核計畫頁整個換掉。「計畫概要」頁籤放報告稿前六段與稽核人員;「評估目標」頁籤左樹右表單,逐條評估目標(AO)填查核方法與指引、受評人員、受評元件/資產、稽核人員。一份 Excel(填寫說明、計畫概要、評估目標、可選清單四頁)匯出就是匯入範本,多值一格寫、換行或分號隔開。匯出三選一:報告稿 docx/pdf、Excel、打包 zip。資料照 OSCAL 評估計畫標準欄位存。本文件寫定案規則、為什麼這樣定、踩到會怎樣。

已實作完成、決策者驗收(2026-10-16) D1~D4、P1~P3 全部定案(2026-10-16) 兩棒八張卡,計畫概要頁籤先出畫面 動兩支套件:jedi-oscal-v2、jedi-compliance-audit 陷阱:set_* 先刪後寫、task uuid 每次重產 P1 改共用縮範圍函式,覆核輪一起變

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

狀態:已實作、決策者驗收(2026-10-16);實作與複測期間改判的規則見下方「實作後改判」,以該節為準|母卡 CM-2799|雛形卡 CM-2800(三輪)|討論稿:discussion.md|雛形:prototype/index.html

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

名詞:AP=稽核計畫(assessment plan);AO=評估目標(assessment objective),控制項底下的檢查項目,全系統用 part_id 當鍵(<控制項代碼>_obj.N;控制項沒有 AO 時退回控制項代碼本身);計畫概要=一份 AP 的表頭(依據、目的、稽核單位、稽核日期起迄、稽核範圍、稽核方法論、稽核人員);activity=OSCAL AP 裡「一個查核動作」,本案一條 AO 一個;稽核次=FR-140 的 compliance.audit_round_passes,一輪內每做一次稽核一筆、每筆自己一份 AP;finding=AR 裡對一條 AO 的判定紀錄。

§1

變更紀錄

日期 變更 對應
2026-10-16 v1:D1~D4、P1~P3 定案;資料模型、Excel 契約、匯出三選一、縮範圍規則、兩棒八張卡、端到端驗收 母卡 CM-2799
2026-10-16 v2:實作完成後補「實作後改判」節(A5、B5、P3 結果、頁籤改名、拿掉控制項預設、附件表稽核人員、匯入可新增稽核人員);內文舊規則就地標註 子卡 CM-2810、CM-2814、測試棒

§2

分工概述(30 秒版)

整案一句話:稽核計畫頁換成「計畫概要」+「評估目標」兩個頁籤,顧問逐條 AO 填怎麼查、查誰、誰查;一份 Excel 匯出即匯入範本;一鍵匯出亞航格式報告稿。

卡 做什麼 產出 完成怎麼判定(決策者親手做什麼、看到什麼)
A1 計畫概要存得起來 新表 oscal.ap_terms_and_conditions、讀寫 API、稽核單位與日期起迄落點 DEV 呼叫 API 存六段文字、稽核單位、日期起迄,再讀回一致
A2 新稽核計畫頁殼+計畫概要頁籤 FE 新頁、路由改指、稽核次麵包屑 對照雛形計畫概要頁籤:填六段、稽核單位單行、日期起迄、稽核人員,按儲存重整還在;麵包屑顯示第 N 次 內部/外部
A3 報告稿 docx/pdf 匯出 docx 範本、產生器、匯出按鈕 匯出 docx 用 Word 開,看到標題帶稽核次、1~6 段是自己填的內容、第 7 段佔位、附件表;選 pdf 一樣內容
A4 FR-043 docx 匯入改填計畫概要 匯入確認時寫新表 匯入亞航報告稿,計畫概要六段有值,不再塞進說明欄
B1 AP 改逐 AO 存 activity 一條 AO 一個、讀寫 API、AR 讀方法改逐 AO DEV 呼叫 API 存三條 AO 的方法、人員、元件、稽核人員,讀回一致;AR 頁方法顯示不變
B2 評估目標頁籤(後改名「查核項目規劃」) 左樹右表單、控制項預設套用(已拿掉,見「實作後改判」)、篩選、總覽 對照雛形評估目標頁籤操作一遍,存檔重整還在
B3 Excel 匯出匯入 四頁 Excel 匯出、解析、預覽模式、確認 匯出 Excel 改兩格匯回,預覽黃底標出、未知方法與人名就地對應、確認後畫面更新
B4 第 2 次內稽與覆核輪的範圍規則+選單 seed 縮範圍含未判定、AO 承襲、「現場觀察」可選 第 1 次內稽判 1 條不合格、留 1 條控制項全沒判,第 2 次計畫出現這兩條控制項;後台方法選單看得到「現場觀察」

依賴:A1 → A2、A3、A4;B1 → B2、B3;B4 只依賴 B1。A 線與 B 線可平行(A 碰 terms 表與 docx、B 碰 activity 與 Excel)。最先看得到畫面的是 A2。

%%{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 A[A 線:計畫概要與報告稿]
      A1[A1 terms 表+API] --> A2[A2 新頁殼+計畫概要頁籤]
      A1 --> A3[A3 報告稿匯出]
      A1 --> A4[A4 docx 匯入改落點]
    end
    subgraph B[B 線:逐 AO 與 Excel]
      B1[B1 AP 逐 AO 存] --> B2[B2 評估目標頁籤]
      B1 --> B3[B3 Excel 匯出匯入]
      B1 --> B4[B4 縮範圍規則+選單 seed]
    end
    A2 -.頁殼.-> B2
圖 1 — 八張卡的依賴(箭頭=必須先做)

B2 掛在 A2 的頁殼上;A2 先完成最順,若 B2 先到可先建頁殼、A2 接上。


§3

決策定案

決策 定案 理由 被排除方案
D1 Excel 多值怎麼寫 一格多值。查核方法與指引一格、一行一組「方法:指引」,只認換行分組;人員、元件、稽核人員一格,換行或分號(全形、半形)隔開;稽核人員空白=沿用計畫概要。附「填寫說明」與看得到的「可選清單」兩頁,匯入時對不到清單的值在預覽就地對應 決策者判斷顧問不會用往右延伸的固定格數;業界匯入範本(Salesforce 多選欄位、Jira、ServiceNow)多採一格多值,配填寫說明、可選清單、匯入檢查三件套 固定格數每格一般下拉(表寬約 22 欄,決策者看雛形後否決);xlsm 多選巨集(Mac/LibreOffice 不相容、資安擋);逗號分方法組(與指引內文衝突)
D2 稽核人員在哪一層 計畫概要填整場名單,每條 AO 預帶、可覆寫;Excel 空白=沿用 只在概要層看不出這條誰查;只在 AO 層要每列重填 只在概要層;只在 AO 層
D3 受評對象分幾欄 受評人員一欄、受評系統元件/資產一欄 現況人員與元件混在一個下拉,決策者點名不好用;元件與資產在 OSCAL 同為 subject 維持一欄;拆三欄
D4 方法詞或人名對不到清單 匯入預覽在那一格就地對應到既有選項或略過;同詞一次對應全部生效;全部處理完才能確認。選單由管理員在後台維護 直接擋會卡住顧問;自動新增會讓錯字變選項 直接擋下;匯入自動新增
P1 第 2 次內稽的範圍 上一次內稽 AR 中判「不合格」的 AO,以及沒有任何判定的 AO,其所屬的整條控制項帶入;判「合格」的不帶。覆核輪用同一份函式、同一條規則 整條控制項帶入讓顧問看得到前後脈絡,且與 FR-140、覆核輪既有粒度一致;沒判定代表沒人查過,不帶就從頭到尾漏掉,到外稽才爆 只留不合格的 AO;不帶未判定(現況)
P2 報告稿附件表 多印「受評對象」「稽核人員」兩欄,匯出時可勾選不印 顧問交報告時這兩欄有用;亞航原表沒有,所以給關閉選項 不印
P3 方法選單 經 system_menus seed migration:「檢視」改名「檢視/現場觀察」,不新增選項(決策者 2026-10-16 裁甲) 選項是客戶與顧問的事,進 system_menus 不寫死;DEV 詞庫已把「現場實地觀察」對到「檢視」 FE 或 BE 寫死選項
頁籤名 「計畫概要」 「場次」決策者看不懂;「稽核大綱」易誤會成目錄;「計畫說明」太空泛 場次、稽核大綱、計畫說明
稽核單位 單行,只填公司名稱;報告稿印成「由○○協助辦理。」 決策者:使用者只會填公司名稱 多行文字
內外稽核 每一次稽核各自一份 AP;頁首麵包屑帶「第 N 次 內部/外部」可切同輪其他次(已結束唯讀、未開始不可點);AP 內容不放內外欄位 FR-140 已把 AP 掛在稽核次;要分的是進入點不是內容 AP 內加內外欄位
AR 另開 FR 改逐 AO 判定與匯出;本案只把 AR「從 AP 讀方法」改逐 AO(AR 逐 AO 已另開 FR-142) 範圍控制;Excel 的佐證、稽核結果兩欄等 AR 案接上 本案一併改 AR 畫面

實作後改判

實作與決策者複測期間定下或推翻的規則。與上表或下方內文衝突時,以本節為準。

項目 定案 理由 取代/被排除
A5 docx 匯入與逐 AO 衝突 docx 匯入只寫計畫概要與稽核人員,不碰行程與 AO A4 與 B1 各自驗收都過,合在一起時 docx 匯入呼叫舊 set_tasks 先刪後寫,把逐 AO 資料清掉且無錯誤訊息 取代 A4 原本「確認時一併寫行程」;被排除:匯入後重建 AO(會覆蓋顧問已填的內容)
B5 受評人員當場新增 存在這份 AP 自己的 metadata party,角色 assessed;同名回既有那個人;不寫回 SSP。稽核人員選項依角色過濾,受評人員不會出現在稽核人員下拉 AP 讀的是 SSP 凍結快照,且稽核期間資源庫鎖住不能寫 被排除:加進 SSP
P3 方法選單 決策者裁甲:「檢視」改名「檢視/現場觀察」,不新增第七項 與 NIST 三法對得起來、選項不重複 取代驗收第 8 條「看得到『現場觀察』」→ 改為看得到「檢視/現場觀察」
頁籤改名 第二個頁籤「評估目標」改名 「查核項目規劃」(en「Assessment Item Planning」);Excel 第三頁同名,匯入時新舊頁名(「評估目標」)都認 這頁是逐條排「用什麼方法查、查誰、誰去查」,「評估目標」易誤會成只列 AO 畫面裡指單一 AO 的「評估目標」字樣(如「已完成 12/30 項評估目標」、Excel 欄名)不改
**拿掉「控制項預設」 查核項目規劃頁點控制項時,沒有**控制項預設區塊;只有實務要求說明、AO 一覽、上一項/下一項。系統不存控制項層,Excel、總覽、Word 附件表也都只有逐 AO 「控制項預設」只是不存檔的一鍵複製,畫面上看起來像可設定控制項層,讓決策者以為有存、進而要求四處都要有控制項列 取代 B2「控制項預設套用」與驗收第 4 條;曾做的 Excel「整個控制項」列已撤回。若日後要控制項層,要另案設計「存不存、AO 是否沿用、改了是否同步」
附件表稽核人員 AO 沒有個別覆寫稽核人員時,Word/PDF 附件表印計畫概要名單(與畫面 effectiveAuditors 同規則);Excel 不變,空白=沿用 原本只印覆寫值,沒覆寫的列整欄空白 —
Excel 匯入對不到的人 受評人員與稽核人員都可「當場新增」;預設就是新增,橫幅顯示「將新增 N 位人員」;稽核人員確認時 AP 有同名非受評人員就沿用並設成 assessor,否則新建。元件/資產與查核方法仍要手動對應 決策者:「沒有就增加」「沒有對應的都幫我標注新增預設」 取代 D4 中「人名對不到就地對應到既有選項或略過」的人員部分;方法詞照 D4
匯入預覽篩選 進出預覽一律停在「全部」 自動切「待處理」時,若待對應只在計畫概要頁籤,查核項目規劃左樹整片空白,看起來像匯入失敗 —

§4

現況接入點

① 資料模型(套件 jedi-oscal-v2,schema oscal,pkg/jedi-oscal-v2/infra/model/ap/)

表 現況 本案動作
assessment_plans(oscal_ap.py:26) uuid、metadata_id、status、import_ssp_id 不改
ap_reviewed_controls(:22) control_selections 必填;control_objective_selections 從未寫入,只在 clone 時原樣搬 B1 寫入本 AP 全部 AO 的 part_id
ap_assessment_activities(:23) steps JSONB({uuid,title,description,props[method]})、related_controls(只寫 control-selections)、responsible_roles B1 一條 AO 一個 activity
ap_tasks(:24) timing、associated_activities、subjects 等 JSONB A1 一份 AP 固定一個 task 放日期起迄;B1 用 associated_activities[].subjects 放每條 AO 的受評對象
ap_task_subjects、ap_task_participants(:25) FR-040 行程層連結表 新頁不再寫;表與舊資料保留
ap_assessment_subjects AP 層受評對象 保留,存檔時由 AO 聯集回填(沿用 FR-040 做法)
oscal.parties 稽核人員是 person 型 party,props responsible-role=assessor(assessment_plan_app_service.py:70-71) A1 稽核單位存 organization 型 party
無表:terms-and-conditions FR-037 差距審查時略過 A1 新增

套件服務 pkg/jedi-oscal-v2/app/service/ap/assessment_plan_service.py(215 行)的 set_reviewed_controls(:113)、set_assessment_subjects(:153)、set_tasks(:184)一律整批先刪後寫,task uuid 每次重產。

jedi-oscal-v2 在主專案目前是 pin 2.6.0(pyproject.toml:85),path dependency 註解在 :139。要改套件的卡先切 path、收口時還原 pin 並發版。jedi-compliance-audit 已是 path(:124,pin 在 :70 註解)。

② 主專案 AP 服務與端點

接入點 現況 本案動作
端點 api/project/routes/audit_round_route.py:144-205 GET /audit-round/<round_uid>/ap、PUT /ap/<ap_uid>/reviewed-controls、…/assessment-subjects、…/tasks、`GET POST …/parties`
app/flow_control/service/assessment_plan_app_service.py(750 行,近上限) _METHOD_PROP(:68)、_methods_to_props(:115)、指引放 steps(:130-165)、related_controls 只到控制項(:169-178)、_ap_detail(:247)、clone_ap_for_round(:330)、set_tasks(:583)、resolve_ap_and_check_auditor(:190) 新邏輯一律新檔,原檔一行呼叫;權限沿用 resolve_ap_and_check_auditor(auditor/manager,過了規劃階段唯讀)
AR 反推 AP pkg/jedi-compliance-audit/app/service/assessment_result_app_service.py:303(檔 860 行已超標) 把 activities 反推成 {control_id: {methods, guidance, subjects}} B1 改逐 AO,新邏輯開新檔
縮範圍 assessment_result_app_service.py:360 narrowed_control_ids_from_parent 只收 target_status_state == "not-satisfied" 的 AO 所屬控制項(:376-381) B4 改成「不合格或沒有 finding」
共用 clone pkg/jedi-compliance-audit/app/service/audit_round_pass_app_service.py:145 clone_ap_narrowed_to_not_met FR-140 抽出,覆核輪與第 N 次內稽共用 B4 確認 clone 帶 AO 層資料
FR-043 docx 匯入 pkg/jedi-compliance-audit/app/service/ap_docx_import_app_service.py(376 行) adapter 已解析 basis/objective/audit_org/methodology/scope/audit_date(ap_report_parser/base.py:13-31),全部塞進每筆 task 的 description(:288-336) A4 改寫計畫概要
AO key pkg/jedi-compliance-audit/app/service/ao_derivation.py part_id,控制項無 AO 時退回 control_id 全案沿用

③ 可沿用

能力 位置 用法
SSP docx/pdf 匯出 app/oscal/service/export/ssp_export_app_service.py:27、ssp_docx_generator.py(237 行,docxtpl 範本+python-docx 追加)、ssp_libreoffice_converter.py:57(讀 LIBREOFFICE_CMD,逾時 60 秒)、範本由 scripts/generate_ssp_docx_template.py 產 A3 照同套路
公式注入防護 jedi_common/utils/export_safety.py set_text_cell、neutralize_formula A3、B3 所有寫入儲存格
SSP 控制項 Excel 隱藏 A 欄 part_id 慣例 app/oscal/service/ssp_control_impl_import_service.py(export_excel :105、confirm_import :375) B3
AP docx 匯入的 parse job 暫存 pkg/jedi-compliance-audit/infra/model/ap_docx_parse_job.py(ap_docx_parse_jobs 表) B3 照同模式暫存解析結果
FE 匯出 src/composables/useSspDocumentExport.js(downloadBlob :90) A3
FE 稽核次 FR-140 卡 10 已出「列稽核次」API 與 AP/AR/POA&M 讀取可指定 pass A2 麵包屑

④ FE

接入點 現況 本案動作
src/views/project/RoundApAuthoringView.vue(1000 行,超標) 以行程為單位 不改;新頁上線後在 B2 收尾時刪
路由 src/config/router/index.js:1118 project-ap-authoring-round 指向舊頁 A2 改指新頁
API 常數 src/config/api/api.js:212-221 AUDIT_ROUND、AP_AUTHORING 各卡補自己的端點
i18n src/config/locales/i18n/zh-tw/ap-authoring.json 舊頁用 新開 ap-plan.json(zh-tw、en)

§5

詳細設計

資料模型與 OSCAL 落點

畫面欄位 OSCAL 路徑 存哪
依據、目的、稽核範圍、稽核方法論 terms-and-conditions.parts[](name=basis/objective/scope/methodology) 新表 oscal.ap_terms_and_conditions
稽核單位 metadata.parties[type=organization]+responsible-parties[role=assessor] oscal.parties
稽核日期起迄 tasks[0].timing.within-date-range{start,end} ap_tasks.timing
計畫概要稽核人員 metadata.parties[type=person],props responsible-role=assessor oscal.parties(既有做法)
每條 AO 的方法與指引 activity.steps[](description=指引,props method) ap_assessment_activities.steps
AO 本身 activity.related-controls.control-selections[].include-controls[].control-id+control-objective-selections[].include-objectives[].objective-id=part_id ap_assessment_activities.related_controls
每條 AO 的受評人員、元件/資產 tasks[0].associated-activities[activity-uuid].subjects[](type=party/component/inventory-item) ap_tasks.associated_activities
每條 AO 的稽核人員(覆寫時) activity.responsible-roles[role=assessor].party-uuids;空=沿用計畫概要 ap_assessment_activities.responsible_roles
第 N 次 內部/外部 FR-140 稽核次,AP props audit_nature、nature_seq 讀,不寫
控制項預設 已拿掉(見「實作後改判」) —

新表 oscal.ap_terms_and_conditions

欄 型別 說明
id bigserial PK
assessment_plan_id bigint FK → oscal.assessment_plans.id ON DELETE CASCADE
part_name varchar(40) NOT NULL basis/objective/scope/methodology;UNIQUE(assessment_plan_id, part_name)
title text 可空
prose text
sort_order int NOT NULL DEFAULT 0
props、links jsonb
remarks text
created_at、updated_at、created_user、updated_user 同 FR-040 表

主線 migration scripts/sql/2026-10-16-fr141-ap-terms-and-conditions.sql,比照 2026-06-21-fr040-ap-task-link-tables.sql(IF NOT EXISTS、GRANT cm_app、sequence 權限、INSERT schema_migrations)。新增主線 migration → 出貨基線待重產。

套件 model 新增 pkg/jedi-oscal-v2/infra/model/ap/ 對應 ORM、entity、repo、service 方法 set_terms(ap_id, parts)/get_terms(ap_id)(以 part_name upsert,不先刪後寫)。

一份 AP 一個 task

A1 起,新頁寫入時一份 AP 只保留一個 task(type=action,title=「稽核計畫」),放日期起迄與 associated_activities。舊資料可能有多個 task:讀取時取第一個 task 的 timing 當日期起迄;第一次以新頁存檔時合併成一個 task(其他 task 的 associated_activities 搬過來)。

🔴 動手前 grep task uuid 的下游引用(wecm、AR、job、ap_task_subjects、ap_task_participants);set_tasks 先刪後寫會重產 uuid,任何拿 task uuid 當鍵的地方都會斷。B1 改 activities 寫法時對 activity uuid 做同一件事——activity 以 AO part_id 做自然鍵 upsert,不先刪後寫,uuid 才穩定。

讀寫 API

端點 方法 內容
/ap/<ap_uid>/overview GET {basis, objective, scope, methodology, audit_org, start, end, auditors:[{party_uuid,name}], pass:{pass_no, audit_nature, nature_seq}, readonly}
/ap/<ap_uid>/overview PUT 同上可寫欄位;auditors 傳 party_uuid 陣列(新增人員沿用既有 POST /ap/<ap_uid>/parties)
/ap/<ap_uid>/objectives GET `{controls:[{control_id, title, practice, description, objectives:[{part_id, label, text, methods:[{method, guidance}], people:[party_uuid], assets:[{type, uuid}], auditors:[party_uuid]
/ap/<ap_uid>/objectives PUT {objectives:[{part_id, methods, people, assets, auditors}]},只送有改的 AO 也可(以 part_id upsert)
`/ap/<ap_uid>/export?format=docx pdf xlsx
/ap/<ap_uid>/xlsx-imports POST multipart 回 {parse_uid, diff}
/ap-xlsx-import/<parse_uid> GET/DELETE 取回或丟棄解析結果
/ap-xlsx-import/<parse_uid>/confirm POST `{method_mapping:{詞: method_key

全部寫入端點在 app service 層呼叫 resolve_ap_and_check_auditor,GET 走既有讀取守門。error code 進 common/code/grc_error_code.py,前綴 GRC_。

計畫概要頁籤(A2)

照雛形 prototype/ap-authoring.html 計畫概要頁籤:依據、目的、稽核範圍、稽核方法論為多行文字框;稽核單位單行(提示「只填公司名稱」);稽核日期起迄兩個日期時間;稽核人員多選可當場新增。頁首:麵包屑「專案 › 第 N 輪 › 第 N 次 內部/外部稽核 › 稽核計畫」,稽核次膠囊點開列同輪全部稽核次(目前、已結束唯讀、未開始不可點),讀 FR-140 卡 10 的列稽核次 API;稽核性質為 NULL 時顯示「未標示」。頁籤「計畫概要」「評估目標」,評估目標頁籤在 B2 前先放「建置中」佔位。

新頁檔:src/views/project/RoundApPlanView.vue(殼)、src/components/grc/ap-plan/ApPlanOverviewTab.vue、ApPassBreadcrumb.vue、composable src/composables/useApPlan.js。

評估目標頁籤(B2)

照雛形評估目標頁籤:左側樹(控制族群→控制項,完成狀態點、搜尋、篩選「全部/尚未填/已覆寫人員」、完成計數);右側一次一條控制項:實務要求與執行說明唯讀、控制項預設區塊(已拿掉,見「實作後改判」);每條 AO 一張卡(方法組不限數量、受評人員、受評元件/資產、稽核人員預帶概要名單可覆寫可還原、「承襲自第 N 次」標籤改過即消);上一條/下一條只在篩選後可見的控制項間跳;「總覽(唯讀表格)」子檢視。

「填好」=至少一個方法,且受評人員或元件/資產至少一個。

新檔:ApPlanObjectivesTab.vue、ApObjectiveTree.vue、ApObjectiveControlForm.vue、ApObjectiveCard.vue、ApMethodGroups.vue、ApObjectivesOverview.vue、composable useApObjectives.js。每支 Vue 檔 800 行內、script 函式 40 行內。

Excel 契約(B3,D1)

四頁,匯出的檔就是匯入範本;空 AP 匯出就是空白範本。

頁 內容
填寫說明 每欄怎麼填、格內換行快捷鍵(Mac:Option+Enter;Windows:Alt+Enter)、匯入流程
計畫概要 直向「欄位/內容」:依據、目的、稽核單位、稽核日期起、稽核日期迄(YYYY-MM-DD HH:MM)、稽核範圍、稽核方法論、稽核人員(換行或分號)
評估目標 見下表
可選清單 查核方法、受評人員、受評元件/資產、稽核人員四欄,匯出時由 SSP 與 system_menus 帶入

「評估目標」頁:

欄 內容 匯入規則
A(隱藏) part_id 比對鍵;不在本 AP 的 AO→預覽列為「略過的列」
B 序號、C 控制項代碼、D 實務要求、E 執行說明、F 評估目標 灰底唯讀;同控制項 D/E 只在第一列 忽略
G 查核方法與指引 一行一組「方法:指引」 依換行切組;每行以第一個全形「:」或半形「:」切方法與指引;無冒號的行=方法空白、整行是指引;方法詞不在選單→D4
H 受評人員 換行或分號 比對 SSP parties 名稱;對不到→D4
I 受評元件/資產 換行或分號 比對 SSP components+inventory-items 名稱;對不到→D4
J 稽核人員 換行或分號;空白=沿用 與概要名單相同=不覆寫

解析一律串流讀、列數上限(沿用 SSP 匯入的上限常數,先 grep)、檔案大小上限、只收 .xlsx;儲存格寫出一律經 export_safety。解析結果暫存(比照 ap_docx_parse_jobs,新表或沿用同表加 kind 欄由卡上先 grep 決定),確認時以 part_id upsert,只寫有變更的 AO。

預覽在正式頁的「預覽模式」呈現(雛形 prototype/import-preview.html):頂部橫幅計數+確認匯入;左樹「待處理」「有變更」徽章;被改欄位黃底、原值灰字;未知方法詞與人名就地對應;全部對應完才能確認;「略過的列」對話框。

報告稿(A3)

docx 範本 app/flow_control/templates/ap/ap_report_template.docx(由腳本產,比照 SSP),標題「{框架} 第 N 次{內部/外部}稽核計畫」(稽核性質 NULL 時省略「第 N 次…」),段落:一 依據、二 目的、三 稽核單位(「由{稽核單位}協助辦理。」+稽核人員一句)、四 稽核日期(起迄同日印「YYYY年MM月DD日上午HH:MM-HH:MM」,跨日印「…至…」)、五 稽核範圍、六 稽核方法、七 稽核結果(佔位「(評估結果完成後帶入)」);附件「稽核檢查紀錄表」:序號、索引號、實務要求、執行說明、評估目標、查核方法:指引(一行一組)、受評對象(P2 可關)、稽核人員(P2 可關)。B1 之前 AP 尚無逐 AO 資料,附件表以控制項列出、方法欄空白;B1 完成後自動帶入。

pdf 經 SspLibreOfficeConverter。zip=docx+xlsx(xlsx 要 B3 完成;B3 之前 zip 選項隱藏)。

縮範圍規則(B4,P1)

narrowed_control_ids_from_parent(assessment_result_app_service.py:360)改為:

control_ids   = 上一次 AP 的 in-scope 控制項
ao_to_control = 上一次 AP 的 AO→控制項對照
judged        = { finding.target_id: finding.target_status_state }  for 上一次 AR 的 findings
帶入控制項 = { ao_to_control[ao] for ao in ao_to_control if judged.get(ao) != "satisfied" }

即不合格、待判(pending)或沒有 finding 的 AO 所屬控制項帶入。DEV 2026-10-16 實查 oscal.assessment_findings.target_status_state 三種值:pending 113、satisfied 58、not-satisfied 20——只有明確 satisfied 才排除,pending 等同未判定,要帶入。這支同時供覆核輪(launch_reverify)與第 N 次內稽使用,覆核輪一起變。新邏輯開新檔(assessment_result_app_service.py 已 860 行),原處一行委派。

clone 時 AO 層資料(activities、associated_activities.subjects、responsible_roles、terms)一併複製,被縮掉的控制項的 AO activity 不複製;複製來的 AO 在 activity props 記 inherited-from-pass=來源 pass_no,FE 據此顯示「承襲自第 N 次」,使用者改過該 AO 即清掉此 prop。

方法選單 seed(B4,P3)

DEV 選單(2026-10-16 實查 system_menus group AUDIT_METHOD):檢視 EXAMINE、訪談 INTERVIEW、測試 TEST、文件檢查 DOCUMENT_REVIEW、電腦設定比對 COMPUTER_CONFIG_COMPARE、門禁系統設定比對 ACCESS_CONTROL_CONFIG_COMPARE。

🔴 開工前先回寫問決策者:docx 匯入詞庫 pkg/jedi-compliance-audit/app/service/import_adapter/method_keywords.py:11 已把「現場實地觀察」對到 EXAMINE(檢視)。NIST 800-171A 的三法是 Interview/Examine/Test,「現場觀察」就是 Examine 的一種。兩條路:

  • 甲(建議):不新增選項,把 EXAMINE 的顯示名改成「檢視/現場觀察」,詞庫補「現場觀察」。選項不重複、與 NIST 三法對得起來。
  • 乙:新增第七項「現場觀察」ONSITE_OBSERVATION,phase=seed migration(ON CONFLICT DO NOTHING,先例 2026-05-22-seed-oscal-ssp-roles.sql),詞庫「現場實地觀察」改對新項。

兩案都同步 B3 匯入比對與 FE i18n;改顯示名也走 seed migration(UPDATE 該列 key),不寫死。phase=seed 不需重產出貨基線。


§6

端到端驗收

  1. 計畫概要:DEV 開一輪走到稽核計畫填寫站,進新稽核計畫頁,麵包屑顯示第 N 次 內部/外部。計畫概要頁籤填六段、稽核單位「鈊安資安顧問」、日期起迄、兩位稽核人員,儲存、重整還在。DB:oscal.ap_terms_and_conditions 該 AP 四列;oscal.parties 多一筆 organization。
  2. 報告稿:匯出 docx,Word 開看到標題帶稽核次、三段寫「由鈊安資安顧問協助辦理。」、四段日期格式正確、第 7 段佔位、附件表;匯出 pdf 內容相同;關掉 P2 兩欄再匯出,附件表少兩欄。
  3. docx 匯入:匯入亞航報告稿,計畫概要六段有值、行程說明不再重複貼長文。
  4. 逐 AO 填寫:查核項目規劃頁籤逐條 AO 填方法與人員(控制項預設已拿掉),改其中一條 AO 的稽核人員,存檔重整還在;左樹完成數正確;總覽表格內容一致。DB:每條 AO 一個 activity,related_controls 帶 control-objective-selections。
  5. AR 不壞:同一輪進稽核執行,AR 頁的方法顯示與改動前一致(逐 AO)。
  6. Excel:匯出 Excel,四頁齊全、A 欄隱藏;在「評估目標」頁改兩格、把一個方法寫成「文件檢視」、一個人名打錯,匯入:預覽黃底標出兩格、未知方法與人名就地對應,對應完確認,畫面更新。
  7. 第 2 次內稽範圍:第 1 次內稽判 1 條 AO 不合格、另一條控制項的 AO 全部不判,走完改善回規劃再到稽核計畫填寫站:第 2 次 AP 包含這兩條控制項、其餘不在;帶進來的 AO 顯示「承襲自第 1 次」,方法與人員跟第 1 次一樣。覆核輪同規則。
  8. 選單:後台方法選單看得到「檢視/現場觀察」(P3 裁甲,見「實作後改判」);匯入含「現場觀察」的 Excel 對到該項、不再被列為未知。

§7

拆分表

卡 做什麼 碰哪裡 套件 依賴 驗收
A1 計畫概要存得起來:新表+套件 model/repo/service+主專案 app service、route、schema;稽核單位 organization party;單一 task 日期起迄 migration、pkg/jedi-oscal-v2/infra/model/ap/、app/service/ap/、主專案新檔 app/flow_control/service/ap_overview_app_service.py、route jedi-oscal-v2(切 path) — 驗收 1 的 API 部分
A2 新稽核計畫頁殼+計畫概要頁籤+稽核次麵包屑;路由改指 FE 新檔、路由、api 常數、i18n — A1 驗收 1
A3 報告稿 docx/pdf 匯出(含 P2 勾選)+FE 匯出選單 主專案 app/flow_control/service/ap_report_export_service.py、ap_docx_generator.py、範本與產範本腳本、route;FE 匯出 composable — A1 驗收 2
A4 FR-043 docx 匯入確認時寫計畫概要 pkg/jedi-compliance-audit/app/service/ap_docx_import_app_service.py jedi-compliance-audit A1 驗收 3
B1 AP 逐 AO 存:activity 以 part_id upsert、objectives 讀寫 API、assessment_subjects 聯集回填、AR 讀方法改逐 AO pkg/jedi-oscal-v2/app/service/ap/、主專案新檔 ap_objectives_app_service.py、route;pkg/jedi-compliance-audit AR 讀取新檔 jedi-oscal-v2、jedi-compliance-audit — 驗收 4 API 部分、驗收 5
B2 評估目標頁籤(左樹右表單、預設套用、篩選、總覽、承襲標籤);舊頁刪除 FE 新檔;刪 RoundApAuthoringView.vue — B1、A2 驗收 4
B3 Excel 四頁匯出+解析+暫存+預覽模式+確認 主專案 ap_workbook_export_service.py、ap_workbook_import_service.py、route;FE 預覽模式 — B1(預覽模式畫面掛 B2 元件) 驗收 6
B4 縮範圍含未判定(P1)+clone 帶 AO 層與承襲 prop+「現場觀察」seed(P3) pkg/jedi-compliance-audit 縮範圍新檔、assessment_plan_app_service.clone_ap_for_round、seed migration、method_keywords、FE i18n jedi-compliance-audit B1 驗收 7、8

互斥:A1 與 B1 都改 jedi-oscal-v2 AP service,但檔案不同(A1 新 terms 方法、B1 改 activities);同時派時各自只 add 自己的檔。B3 與 B2 都碰 FE 稽核計畫頁,B3 的預覽模式等 B2 元件到位再做畫面部分。B4 改 clone_ap_for_round 時 B1 必須已完成。

收口(不開卡,母卡收尾時做):jedi-oscal-v2 與 jedi-compliance-audit 發版、還原 pin(jedi-compliance-audit 與 FR-140 同批發);出貨基線重產(A1 的主線 migration);docs/spec-site/current/audit-execution/ap-authoring.md 改寫;e2e 另開一棒。


§8

坑與陷阱

# 坑 踩到會怎樣 對策
1 set_* 先刪後寫、uuid 重產 下游拿 task/activity uuid 當鍵的全斷,且無錯誤訊息 A1、B1 動手前 grep 引用;terms 以 part_name upsert、activity 以 part_id upsert
2 AP 掛在稽核次(FR-140) 讀輪次四槽位會讀到別一次的 AP 新碼一律經 FR-140 的 pass domain service 取目前稽核次
3 縮範圍函式是覆核輪與第 N 次內稽共用 改 B4 時覆核輪範圍一起變 這是預期行為(P1 決策適用兩者);B4 回寫要寫明,複測覆核輪
4 target_status_state 有第三個值 pending(DEV 113 筆,比判定過的還多) 照現況只收 not-satisfied,待判的整批漏掉 規則寫成「只有明確 satisfied 才排除」
5 控制項沒有 AO 時 key=control_id Excel 只有一列、activity 的 objective-selections 為空 沿用 ao_derivation.py;匯出匯入都處理此退路
6 指引內文含冒號 「文件檢查:核對 A:B 表」被切錯 只以每行第一個冒號切
7 超標檔:assessment_result_app_service.py 860、RoundApAuthoringView.vue 1000、ProjectAuditorOverview.vue 2128 往裡加會被 reviewer 標阻斷 新邏輯全開新檔,原檔一行委派
8 jedi-oscal-v2 目前是 pin 改了套件但主專案吃 Nexus 版,改動不生效 A1、B1 開工先切 path dependency(pyproject.toml:139),path 改動不 commit
9 Excel 可選清單是匯出當下快照 匯出後 SSP 新增人員,舊檔寫新名字會被當未知 預覽就地對應兜底(D4)