FR-142 · 設計定案 · 2026-10-16

稽核結果重做:設計定案

左樹三層+控制項總覽、一次一條評估目標、稽核報告第 7 章、Excel 只關聯檔案、報告 Word/PDF

稽核階段判定頁整個換掉。左樹控制族群→控制項→評估目標三層;點控制項看控制項總覽,點評估目標依序填佐證、其他佐證、稽核發現、稽核結果、風險(只在不符合時出現);控制項結論由評估目標自動推算。「稽核報告」頁籤只寫第 7 章,預覽與匯出才組完整報告。Excel 是稽核計畫那份往後加結果欄,佐證只關聯檔案。結案整份鎖定、佐證凍結版本。資料照 OSCAL 評估結果標準欄位存,並修 jedi-oscal-v2 三處匯出不合規。本文件寫定案規則、為什麼這樣定、踩到會怎樣。

D1~D12 全部定案(2026-10-16) 六棒七張卡,棒 1 套件修正先做 動兩支套件:jedi-oscal-v2、jedi-compliance-audit 陷阱:佐證候選要走 workflow_execution_control_mapping 二次 scope POA&M 另開 FR,本案只保交接點

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

狀態:設計定案(2026-10-16)|母卡 CM-2818|雛形卡 CM-2819(六輪,Done)|討論稿:discussion.md|雛形:prototype/index.html|範例 Excel:prototype/sample/FR-142-稽核結果範例.xlsx

🔴 實作在 FR-142 worktree:BE ~/Projects/Billows/Audit-Manager/wt-FR-142/compliance-manager-be、FE …/wt-FR-142/compliance-manager-fe、套件 ~/Projects/Jedicogy/module/wt-FR-142/jedi-python-package,皆 branch feature/FR-142(自 FR-139 b4048bd5e);BE 8003 埠、FE 5181、專用庫 guidant_ai_fr142(從 DEV 複製,DEV 不碰;套件測試帶 JEDI_TEST_ENV 指 worktree .env);合回 FR-139 時 DEV 補套套件 013。文件在主工作樹維護。

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

名詞:評估目標=AO(assessment objective),控制項底下的檢查項目,全系統用 part_id 當鍵(<控制項代碼>_obj.N;控制項沒有 AO 時退回控制項代碼);AR=稽核結果(assessment results);finding=一條評估目標的判定;observation=佐證紀錄;risk/remediation=風險/改善建議;attestation=稽核人員署名的文字陳述(第 7 章);稽核次=FR-140 的概念,一輪內每次稽核一筆,各自一份 AP 與一筆 AR result。

§1

變更紀錄

日期 變更 對應
2026-10-16 v1:D1~D12 定案;資料模型與 OSCAL 落點、API、畫面、Excel 契約、報告、六棒七張卡、端到端驗收 母卡 CM-2818

§2

分工概述(30 秒版)

整案一句話:判定頁換成左樹到評估目標、中間一次一條評估目標(佐證→稽核發現→稽核結果→風險),控制項結論自動推算;稽核報告頁籤只寫第 7 章;Excel 延伸稽核計畫、佐證只關聯檔案;結案整份鎖定。

卡 做什麼 產出 完成怎麼判定(決策者親手做什麼、看到什麼)
1a 套件稽核結果存法改成 OSCAL 標準 jedi-oscal-v2 migration、finding/risk/remediation 修正、匯出合規 DEV 判一條不符合、填風險後匯出 OSCAL JSON,用官方 schema 驗證通過
1b 評估目標層判定矩陣、佐證與第 7 章存取、控制項結論規則一支 兩支套件 service 開一個新稽核次,DB 該次 finding 數是 0;判三條後是 3
2 後端讀寫 API、結案守門與鎖定 主專案新 app service、route Swagger 存三條評估目標讀回一致;有未判定時按結案被擋;結案後再存被拒
3 前端新頁 FE 新檔、舊頁退役 對照雛形逐頁操作:左樹、控制項總覽、評估目標頁、稽核報告、預覽
4 Excel 匯入匯出 workbook 結果欄、檔名比對、預覽 匯出 Excel 改兩格、加一個檔名匯回,預覽標出、確認後畫面更新
5 稽核報告 Word/PDF docx 範本、匯出服務 Word 開檔看到 1~7 章與橫式附件,判定是中文
6 FR-140 對齊與選單 縮範圍改評估目標層、選單 seed 第 2 次內稽的新稽核計畫只剩不符合評估目標所屬控制項

依賴:1a → 1b →(2、4、5、6 可平行);3 等 2。

%%{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
    A1[1a 套件 OSCAL 修正] --> B1[1b 評估目標層矩陣<br/>observation/attestation]
    B1 --> C2[2 BE API]
    B1 --> C4[4 Excel]
    B1 --> C5[5 報告]
    B1 --> C6[6 FR-140 對齊]
    C2 --> C3[3 FE 新頁]
    C2 --> C5
圖 1 — 卡片依賴

§3

決策定案

D1 判定值域=符合/不符合/不適用三值,加「未判定」當初始狀態但不算判定

  • 畫面與 Excel 四個選項都看得到;Excel 空白=未判定;送出結案時擋「還有未判定」。
  • OSCAL 存法:符合/不符合 → finding.target.status.state=satisfied/not-satisfied;不適用 → finding.target.implementation-status.state=not-applicable,status.state 同時填 satisfied、reason=other 讓匯出合規;未判定=不建 finding,不落假值。
  • 清掉套件自創的 pending/not-applicable 狀態 token 與 target_status={"product_state":…}。

為什麼:稽核人員現場一定有「還沒查到」的狀態,不能逼他先選;但 OSCAL 的 status.state 是封閉值域(只有 satisfied/not-satisfied),「未判定」「不適用」沒有對應值,硬塞進去匯出就不合規。implementation-status 官方語意是「控制項實作到什麼程度」,not-applicable 是它的正規值,放這裡最貼。

被排除:維持現狀四值全進 status.state(匯出不合規);「不適用」改成不建 finding(報告附件要印 N/A,沒 finding 印不出來)。

D2 風險在評估目標上就地填,只有判不符合時出現;不設風險總覽頁籤

  • 評估目標頁判「不符合」時展開風險區:風險等級(無/低/中/高)、風險說明、改善建議。判符合時收起,留「+ 新增風險」連結給「符合但有疑慮」的少數情況。
  • 存法:每條填了風險的評估目標自動生一筆 risk(status=open),用 assessment_finding_risks 連結表綁到該 finding;改善建議存 risk.remediations[] 一筆 lifecycle=recommendation。
  • 判準「有風險」=風險等級或風險說明任一有值;有風險的評估目標,所屬控制項判不符合(D5)。

為什麼:顧問習慣一列填完;「先建風險再勾評估目標」是現在頁面難用的主因。大多數評估目標是符合,風險區收起畫面短一半。

被排除:維持「風險獨立建、再勾」為主路徑(難用);風險總覽頁籤與合併多筆(雛形第一版有,決策者看過後拿掉:風險已逐評估目標呈現、報告附件也逐列印,合併功能 Excel 無法來回)。

D3 「稽核報告」頁籤只寫第 7 章;1~6 章與附件只在預覽與匯出時組出完整報告

  • 頁籤只有一個大編輯器,像在 Word 打第 7 章一樣寫,想分幾點分幾點。
  • 「帶入不符合事項」把判不符合的評估目標依「控制項代碼 標題 [評估目標]:稽核發現/風險(等級)/改善建議」格式接在現有內容後面;無不符合時提示「無不符合事項」。
  • 「預覽報告」開全螢幕:第 1~6 章來自稽核計畫(FR-141 的 ap_terms_and_conditions 等),第 7 章為本編輯器內容,附件為逐評估目標結果表(D11)。
  • 存法:整段存 ar_results.attestations[] 一筆,parts[] 一個 part(name 固定 audit-conclusion),responsible-parties 帶稽核人員。

為什麼:1~6 章與附件放在頁籤裡頁面太長,使用者會誤以為沒地方寫;第 7 章才是這一頁要做的事。OSCAL 的 attestation 官方語意是「評估者寫的文字陳述」,比 result.description 或 remarks 貼切。

被排除:固定三個子節(看不懂);左右分欄唯讀 1~6 章(難看);1~6 章與附件全攤在頁籤裡(太長)。

D4 舊資料不遷移,只影響新開的稽核次

  • 稽核次開始時若該次 AR result 已有 finding(舊控制項層)就維持舊讀法;新開的稽核次一律 AO 層。判斷依據:finding 的 target_id 是否含 _obj.。
  • 舊頁 RoundAuditReviewView.vue 退役後,既有控制項層稽核次用新頁唯讀顯示(每控制項一條合成卡),不開放編輯。

為什麼:舊輪資料只有 DEV 與少數測試專案;把控制項判定拆到每條 AO 要猜,猜錯比不猜糟。FR-140 已有「每次稽核一筆 result」的分界點可借用。

D5 控制項結論不存欄位,由 AO 即時推導;三處各自實作收斂成套件一支

  • 規則:任一 AO 不符合或掛風險 → 不符合;全部 AO 不適用 → 不適用;還有未判定 → 未判定;其餘 → 符合。
  • 落點:jedi-compliance-audit 一支純函式(ar_import/aggregation.py 的 aggregate_verdict 擴充「掛風險」條件),FE 不再自己算、FR-140 縮範圍規則改呼叫它。
  • 統計(控制項數/符合/不符合/不適用/未判定/風險數)由 BE 回傳,FE 只顯示。

為什麼:現在 FE(RoundAuditReviewView.vue:411)、BE 匯入(aggregation.py:10)、縮範圍(pass_scope_rule.py:12)三套規則各寫一次,「掛風險也算不符合」是新條件,三處改必漏。OSCAL AR 本來就沒有控制項層判定欄,存了反而要維護一致性。

D6 左樹到評估目標三層,點控制項看控制項總覽

  • 左樹:控制族群 → 控制項 → 評估目標,與專案規劃頁同(FR-141 稽核計畫只到控制項,是 FR-141 做淺了)。
  • 燈號放最前面,顏色加形狀區分四態,色弱可辨;hover 顯示「稽核結果:X」與全文;樹上方一列圖例。評估目標文字最多兩行。
  • 點控制項:中間顯示控制項總覽(結論、依據、計數、逐評估目標結果表);點表格列進評估目標頁;評估目標頁頁首有返回箭頭。
  • 中間頁首「控制項結論」保留文字膠囊。

為什麼:右側膠囊把評估目標與控制項說明擠到被截斷;中間一次只放一條評估目標,資訊才不會擠在一起;點控制項要看得到控制項結果。

被排除:膠囊放右側(擠);文字無限換行(樹太長);點控制項直接跳第一條評估目標(看不到控制項結果)。

D7 評估目標頁照稽核人員實際做事的順序:佐證 → 其他佐證 → 稽核發現 → 稽核結果 → 風險

  • 欄位:佐證(清單)、其他佐證(訪談、現場觀察等文字)、稽核發現、稽核結果*(四選一)、風險(D2)。
  • 「其他佐證」與佐證清單分開;拿掉檔案旁的備註框——要指出檔案哪一頁寫在稽核發現。

為什麼:稽核人員是先看證據、寫下看到什麼、最後才判過不過;第一版把判定放最上面等於先下結論。備註框與「其他佐證」太像,使用者分不出來。

D8 佐證=這條評估目標對到的任務佐證(檔案、連結、問卷),一張平的清單,預設全部採用,取消勾選=不採用

  • 來源:public.workflow_execution_control_mapping 對到該控制項/評估目標的任務,底下 compliance.job_evidences 類型 FILE/LINK/SURVEY 的項目,合成一張清單、同筆去重;不分任務顯示,每列後面標上傳者與日期。
  • 只有檔案、連結、問卷可採用;任務討論(job_execution_comments)與報告類型不列。
  • 「加入其他佐證」:從整個專案其他任務的佐證挑;「現場上傳」:稽核人員現場拍的照片,存為本次稽核的佐證,清單上標「現場」。
  • 「採用」的語意:記在稽核結果這邊,表示「本評估目標判定採用這些證據」,不改任務端資料、不標有效無效;同一份證據可被多條評估目標各自採用。
  • 判「符合」但沒有任何佐證:提示「未附佐證」,不擋。

為什麼:舊頁 RoundAuditReviewView 是「上面頁籤瀏覽、下面觀察表單另外多選關聯」兩塊;併成一張清單,看與勾在同一處。稽核人員不需要知道任務分組;討論是溝通紀錄、不是證據,有用的內容應寫進稽核發現。

被排除:自編證據池(第一版,未對應系統資料);依任務分組(看不出需要);討論可勾選採用(決策者裁:只供參考,後再裁拿掉);回到舊頁頁籤式(一條評估目標佐證通常三五筆,分頁籤要一直切)。

D9 Excel:與稽核計畫同一份四頁,計畫欄灰底唯讀,後接結果欄;佐證只關聯檔案

  • 結果欄:稽核結果、佐證檔案、其他佐證、稽核發現、風險等級、風險說明、改善建議。
  • 佐證檔案一行一個檔名,只含檔案與現場上傳;刪一行=不採用、加一行=採用。連結與問卷只在畫面設定,Excel 不出現也不讀。
  • 「可選清單」頁新增「佐證檔案」欄,列出專案內可關聯的檔名供複製。
  • 匯入時檔名比對專案檔案;對不到或同名多筆時,預覽頁就地處理:對應到某個檔案/改為其他佐證文字/略過。

為什麼:稽核人員不會照「類型:名稱」格式寫;檔名是唯一實際會出現在 Excel 的證據形式,也是亞航附件的寫法。

D10 結案=本次稽核整份鎖定

  • 結案守門三條:尚有評估目標未判定、不符合項目未填稽核發現(或有風險等級未填風險說明)、第 7 章未填寫。任一成立即擋,列出原因。
  • 通過後再確認一次;結案後判定、風險、佐證、第 7 章全部唯讀,畫面顯示「本次稽核已於 YYYY-MM-DD 結案,內容不可修改」。
  • 結案時凍結每筆採用佐證當下的版本(檔案記雜湊值與版本;連結記網址;問卷記填答快照識別),之後受稽單位更換檔案不影響稽核紀錄。
  • 要改只能在下一次稽核重做(FR-140 流程)。

為什麼:決策者原話「過了就過了,那不是結案報告,不然一直改等等會有爭議」。結案後產生 POA&M,若仍可改判定會與 POA&M 對不上。

D11 報告附件橫式 8 欄、判定印中文

  • 正文 1~7 章直式;附件獨立一節橫式(docx 分節換方向)。
  • 8 欄:序號、索引號、實務要求與執行說明(合一欄)、控制目標、查核方法:指引、佐證資料、稽核結果、稽核發現與風險(合一欄)。
  • 稽核結果印「符合/不符合/不適用/未判定」,不印 MET。不符合列淡紅底。
  • 預覽與匯出可取消「含稽核發現與風險」,取消後為亞航原表 7 欄。

為什麼:10 欄直式 A4 每欄不到 2 公分不可讀;合併兩組欄後與亞航原表寬度相近。

D12 表單文案用專業用語,不放說明句

  • 欄位、按鈕、提示訊息用表單慣用詞(佐證、稽核發現、稽核結果、風險等級、改善建議、選擇文件、上傳、結案、已儲存、匯入完成)。
  • 不放 placeholder 範例句、欄位旁說明句、頁首說明列;驗證訊息寫成「尚有 N 項評估目標未判定」「不符合項目須填寫稽核發現」這類格式。
  • 判定類 badge 與燈號全頁同一套樣式;不使用左側粗色邊框卡片(見 CLAUDE.md UI 禁令)。

§4

現況接入點

座標以 2026-10-16 為準,runner 開工先開檔核一次。FR-141 已完整併入 feature/FR-139(git merge-base --is-ancestor 成立),其頁面骨架、六支 app/flow_control/service/ap_*.py、docx 範本都在主工作樹。

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

表 現況 本案動作
assessment_results(oscal_ar.py:27) uid、metadata_id、status、import_ap_id、local_definitions 不改
ar_results(oscal_ar_results.py:25) 每稽核次一筆(FR-140);reviewed_controls NN、attestations/assessment_log JSONB 無任何 service 寫入 棒 1:加 attestation 寫入 service;reviewed_controls.control-objective-selections 寫入本次 AO 清單
assessment_findings(oscal_ar_finding.py:26) target_type 永遠 objective-id 但新輪 target_id 存 control_id;target_status_state 存自創 token(pending/not-applicable);target_status JSONB 存 {"product_state":…};沒有 target.status.reason/implementation-status/target.description 欄 棒 1:每 AO 一筆、target_id=part_id;target_status JSONB 改存 OSCAL 形狀 {state, reason, remarks};加 implementation_status JSONB;description 存判定理由
assessment_observations(oscal_ar_observation.py:26) 欄位齊全(methods NN、relevant_evidence、subjects、collected);無 service;finding→observation 只有 JSONB 軟參照 棒 1:加 observation service;每條有發現或佐證的 AO 一筆,methods 從 AP activity 的方法帶、relevant_evidence[].description 放佐證文字
assessment_risks(oscal_ar_risk.py:25) characterizations 被寫成 {"severity":…}(assessment_risk_service.py:99-100),不是 OSCAL 陣列形狀 棒 1:改 [{origin:{actors:[稽核人員]}, facets:[{name:'severity', system:'http://csrc.nist.gov/ns/oscal', value:'高'}]}]
assessment_finding_risks(oscal_ar_finding_risk.py:25) link_findings 只寫這張表;匯出讀 finding.related_risks JSONB,沒同步,service 建的關聯匯出會消失 棒 1:寫連結表時同步回填 JSONB(或匯出改讀連結表,二擇一,建議後者)
assessment_remediations(oscal_ar_remediation.py) lifecycle 可空、註解寫 recommendation/required;官方必填且值域 recommendation/planned/completed 棒 1:NOT NULL+CHECK 三值;建議一律 recommendation
ar_assessment_subjects AR 層受評對象 不改

② 套件服務與主專案端點

元件 現況 本案動作
pkg/jedi-oscal-v2/domain/service/ar/ar_finding_matrix_service.py:40-48 init_finding_matrix 每個 id 一筆 finding、初始 pending;_STATE_TO_TOKEN 四值對照 棒 1:初始化不建 finding(未判定=無 finding);upsert 改寫 OSCAL 形狀
pkg/jedi-oscal-v2/app/service/io/oscal_io_service.py:1603-1608 匯出時 target_status 有值就原樣吐進 target.status 棒 1:改組 {state, reason}+implementation-status
pkg/jedi-compliance-audit/.../assessment_result_app_service.py(860 行,超標) init_ar_matrix_for_round(:204)傳 control_ids;judge_finding(:536);observation/risk/remediation CRUD(:572~:802);finalize_assessment(:834)看 pending 數擋 棒 1:init 改傳 AO part_id 清單;棒 2:新開檔 ar_objective_app_service.py 放整批存、統計、結案守門,不往 860 行裡加
pkg/jedi-compliance-audit/.../ar_import/aggregation.py:10 aggregate_verdict 四規則,只有 FR-044 匯入在用 棒 1:加「掛風險」條件,成為 D5 唯一規則
pkg/jedi-compliance-audit/.../pass_scope_rule.py:12 carried_control_ids:AO 沒自己的 finding 時退回控制層 棒 6:直接讀 AO 層 finding+D5 規則
主專案 route api/project/routes/audit_round_route.py:235-395 /audit-round/<round_uid>/ar/findings(GET)、finding/<uid>(PUT 單筆)、observations/risks CRUD、risk/<uid>/findings(PUT) 棒 2:加 PUT …/ar/objectives(整批)、PUT …/ar/attestation、GET …/ar/summary、POST …/ar/finalize 守門改版;舊單筆端點留給舊稽核次
ar_import_route.py+ar_import_app_service.py(621 行)+airasia_cmmc_l1_ar_v1.py FR-044 的亞航格式 xlsx 匯入(序號、索引號、實務要求、說明、AO、方法、佐證、稽核結果) 棒 4:新主路徑是 FR-142 格式(有 part_id);亞航原格式 parser 留作「沒有 part_id 的舊檔」退路

③ FE 現況

元件 現況 本案動作
src/views/project/RoundAuditReviewView.vue(1,083 行,超標) 路由 /project/projects/:id/round/:roundUid/audit;兩頁籤「控制項稽核判定」「風險」;讀 ao_findings 聚合成控制項狀態(:409-424)、存檔把同一判定寫到該控制項全部 finding(:551-555);風險建在「風險」頁籤再勾 finding 棒 3:全新檔 RoundAuditResultView.vue+ar-result/ 子元件(左樹、控制項總覽、評估目標頁、稽核報告、報告預覽),路由改指;舊檔退役(D4 舊稽核次用新頁唯讀)
src/components/grc/ar-import/ArImportDialog.vue(720 行) FR-044 匯入對話框 棒 4:改成預覽模式(同 FR-141 做法,不另開清單)
FR-141 的 ObjectiveTab.vue 等左樹右表單元件 已在主工作樹 棒 3:抽共用(左樹、控制項頁首、上一條下一條)或直接 copy,由 runner 看耦合度決定

④ 可沿用的匯出匯入能力

能力 座標 本案動作
稽核計畫 Excel 讀寫六支 app/flow_control/service/ap_workbook_{layout,writer,reader,diff,export_service,import_service}.py 棒 4:layout 加 COL_VERDICT…COL_REC 六欄常數;writer/reader 加結果欄;diff 加結果欄比對;新開 ar_workbook_export_service.py/ar_workbook_import_service.py 組合
報告稿 docx+pdf app/flow_control/service/ap_docx_generator.py、ap_report_export_service.py、範本 app/oscal/templates/ap/ap_report_template.docx、SspLibreOfficeConverter 棒 5:範本複製成 ar_report_template.docx(第 7 章佔位改真內容、附件表橫式加欄);新開 ar_report_export_service.py
解析暫存 oscal.ap_docx_parse_jobs,source_type 區分 棒 4:source_type='ar_xlsx'

§5

詳細設計

OSCAL 落點(AR,v1.2.2)

畫面欄位 OSCAL 落表
稽核結果 result.findings[](每評估目標一筆):target.type=objective-id、target.target-id=part_id、target.status.state=satisfied/not-satisfied、target.status.reason=pass/fail/other、target.implementation-status.state=not-applicable(不適用時);未判定=不建 finding assessment_findings:target_id、target_status(JSONB 改 OSCAL 形狀)、新欄 implementation_status
稽核發現 finding.description(必填;符合且空白時系統補「經檢視佐證,符合本評估目標」)、finding.title=評估目標文字 assessment_findings.description/title
佐證(採用的檔案、連結、問卷、現場上傳) observation 一筆:methods=EXAMINE、relevant-evidence[] 每筆 href 指向該系統物件(檔案預覽、連結網址、問卷填答)、description=名稱;collected=存檔時間;finding 用 related-observations 連 assessment_observations(relevant_evidence JSONB)
其他佐證 observation 另一筆:description=文字、methods 照稽核計畫該評估目標的查核方法映射(訪談→INTERVIEW、文件檢查/現場觀察→EXAMINE、測試/設定比對→TEST) assessment_observations
風險等級 risk.characterizations[]:{origin:{actors:[稽核人員]}, facets:[{name:'severity', system:'http://csrc.nist.gov/ns/oscal', value:'低/中/高'}]} assessment_risks.characterizations(陣列)
風險說明 risk.title(取說明前段)、risk.description=risk.statement=說明全文、risk.status=open assessment_risks
改善建議 risk.remediations[] 一筆:lifecycle=recommendation、title/description=建議文 assessment_remediations
評估目標↔︎風險 finding.related-risks[] assessment_finding_risks 連結表(匯出改讀此表)
第 7 章稽核結果 result.attestations[] 一筆:parts[{name:'audit-conclusion', prose:全文}]、responsible-parties=稽核人員 ar_results.attestations JSONB
本次判定範圍 result.reviewed-controls.control-objective-selections[]=本次評估目標 part_id 清單 ar_results.reviewed_controls
第 1~6 章 從 AP 讀(FR-141) 不重存
控制項結論 無欄位,D5 即時推導 —

官方必填核對(v1.2.2 JSON schema):finding(uuid/title/description/target,target 再需 type/target-id/status)、observation(uuid/description/methods/collected)、risk(uuid/title/description/statement/status)、remediation(uuid/lifecycle/title/description)、result(uuid/title/description/start/reviewed-controls)、attestation(parts)——上表每一項都有來源,畫面不需多填。

%%{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
    AO[評估目標頁<br/>佐證/其他佐證/稽核發現<br/>稽核結果/風險] --> F[finding<br/>target-id=part_id<br/>status.state / reason<br/>implementation-status]
    AO --> O1[observation 佐證<br/>EXAMINE<br/>relevant-evidence=檔案/連結/問卷]
    AO --> O2[observation 其他佐證<br/>methods 從稽核計畫帶<br/>description=文字]
    AO -->|判不符合且有填| K[risk<br/>characterizations.facets severity<br/>status=open]
    K --> M[remediation<br/>lifecycle=recommendation]
    F -.related-observations.-> O1
    F -.related-observations.-> O2
    F -.related-risks 連結表.-> K
    T[稽核報告頁籤第 7 章] --> A[result.attestations<br/>part name=audit-conclusion]
圖 2 — 一條評估目標存成哪些 OSCAL 物件

讀寫 API(棒 2)

全部掛在既有 /audit-round/<round_uid>/ar 下,讀取時以 FR-140 的「目前稽核次」取 AR result(GET 可帶 pass 讀指定稽核次)。新邏輯一律新檔 app/flow_control/service/ar_objective_app_service.py,不往套件 824 行的 assessment_result_app_service.py 加。

方法 路徑 用途
GET …/ar/objectives 控制項樹(族群→控制項→評估目標)、每條評估目標的稽核結果/稽核發現/其他佐證/風險、候選佐證清單與採用狀態、控制項結論(D5)、統計六格、AP 參考(查核方法、受評對象、稽核人員)
PUT …/ar/objective/<part_id> 存一條評估目標(稽核結果、稽核發現、採用佐證 id、加入其他佐證 id、現場上傳 id、其他佐證文字、風險等級/說明/改善建議);回傳該控制項最新結論與統計
POST …/ar/onsite-evidence 現場上傳(multipart),存為本次稽核佐證,回傳 id
GET/PUT …/ar/conclusion 第 7 章文字(attestation)
GET …/ar/conclusion/draft 「帶入不符合事項」草稿文字
POST …/ar/close 結案:守門三條(412 列原因)→ 凍結佐證 → 呼叫既有 finalize_assessment(有不符合生 POA&M)→ 鎖定
GET …/ar/export?format=docx|pdf|xlsx|zip&with_findings= 匯出三選一(棒 4、5)
POST …/ar-xlsx/parse、/ar-xlsx/<parse_uid>/confirm Excel 匯入預覽與確認(棒 4)

鎖定:結案後所有寫入端點回 409(新 error code,GRC_409xxx)。權限:寫入要專案稽核人員角色,走 common/authz 資源域守門(同既有 AR 端點)。

候選佐證:public.workflow_execution_control_mapping(workflow_execution_id, version_id, control_id, ao_part_id)對到該控制項的工作流程 → compliance.job_evidences 類型 FILE/LINK/SURVEY;二次 scope:只留 workflow_execution_id 真的對到該 control_id 的(memory feedback_evidence_per_control_scope_wf_control_mapping);同 uid 去重。relevant-evidence 存字串 file_uid,不存數字 file_id。

採用狀態存法:observation(佐證那筆)relevant_evidence[] 存「被採用」的完整清單(不是存排除清單),讀取時「候選且在清單中=勾選」。首次讀取該評估目標時尚無 observation,畫面視為全部勾選;第一次存檔才寫入。

畫面(棒 3)

照雛形 prototype/ar-authoring.html。FE 新檔 src/views/project/RoundAuditResultView.vue 加 src/components/grc/ar-result/ 子元件:ArObjectiveTree.vue(左樹三層+燈號+圖例)、ArControlOverview.vue、ArObjectiveForm.vue(佐證清單、其他佐證、稽核發現、稽核結果、風險)、ArEvidencePicker.vue、ArConclusionTab.vue、ArReportPreview.vue。左樹可沿用 FR-141 src/components/grc/ap-plan/ApObjectiveTree.vue 的結構但需擴到評估目標層。路由 /project/projects/:id/round/:roundUid/audit 改指新頁;舊 RoundAuditReviewView.vue(1,083 行)退役。D4:舊稽核次(finding target_id 不含 _obj.)用新頁唯讀。

Excel 契約(填寫說明、計畫概要、評估目標、可選清單四頁,延伸 FR-141)

「評估目標」頁欄序:

欄 內容 規則
A(隱藏) part_id 比對鍵;不在本次稽核範圍的列略過
B~J 序號、控制項代碼、實務要求、執行說明、評估目標、查核方法與指引、受評人員、受評元件/資產、稽核人員 FR-141 計畫欄,灰底唯讀,匯入忽略
K 稽核結果 下拉:符合/不符合/不適用;空白=未判定;無效值預覽就地對應
L 佐證檔案 一行一個檔名,只含檔案;匯出列已採用者;匯入比對專案檔案,對不到預覽三選一
M 其他佐證 自由文字
N 稽核發現 不符合必填(匯入不擋,結案才擋)
O 風險等級 下拉:低/中/高;空白=無
P 風險說明 與 O 任一有值=有風險
Q 改善建議 → remediation

「可選清單」頁:稽核結果、風險等級、佐證檔案三欄(黃底),其餘沿用 FR-141。「計畫概要」頁唯讀、匯入不讀。範例:prototype/sample/FR-142-稽核結果範例.xlsx(產生腳本 make_sample_xlsx.py)。

匯入預覽(prototype/import-preview.html):同一畫面預覽模式;異動的評估目標標示、原值劃掉;無效值與找不到的檔名標「待確認」就地處理;對不到 part_id 的列略過;全部處理完才能匯入。

報告(棒 5)

匯出三選一

GET /audit-round/<round_uid>/ar/export?format=docx|pdf|xlsx|zip&with_findings=true|false:

  • 稽核報告(Word/PDF):標題帶稽核次;第 1~6 章從 AP 帶;第 7 章=attestation 全文;附件依 D11。
  • 稽核結果工作表(Excel):上面四頁。
  • 全部檔案(ZIP):docx+xlsx。

範本:複製 app/oscal/templates/ap/ap_report_template.docx 為 ar_report_template.docx(打包只收 app/oscal/templates/,不要放 flow_control);正文 1~7 章直式,附件獨立 section 橫式,8 欄依 D11。生成沿用 FR-141 app/flow_control/service/ap_docx_generator.py 的寫法、pdf 沿用 SspLibreOfficeConverter。新檔 ar_report_export_service.py、ar_docx_generator.py。

縮範圍規則(棒 6)

pkg/jedi-compliance-audit/app/service/pass_scope_rule.py(28 行)carried_control_ids 改讀評估目標層 finding,套 D5 的同一支規則:控制項結論為不符合者帶入下一次稽核。舊稽核次(控制項層 finding)維持原邏輯。


§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
    participant U as 稽核人員
    participant FE as 稽核結果頁
    participant BE as BE ar_objective_app_service
    participant PKG as jedi-oscal-v2 AR services
    U->>FE: 進入稽核次(status=auditing)
    FE->>BE: GET /ar/objectives
    BE->>PKG: 讀 AP activities(AO 清單+方法)+ 既有 findings/observations/risks
    PKG-->>BE: 合成逐 AO 列(未判定=無 finding)
    BE-->>FE: AO 列+控制項結論(D5)+統計
    U->>FE: 逐條判定、填發現/佐證/風險/建議;或匯入 Excel
    FE->>BE: PUT /ar/objectives(整批)
    BE->>PKG: upsert finding(OSCAL 形狀)/observation/risk+remediation/連結表
    U->>FE: 稽核報告頁籤寫第 7 章(可帶入不符合事項)
    FE->>BE: PUT /ar/attestation
    U->>FE: 送出結案
    FE->>BE: POST /ar/finalize
    BE->>BE: 守門:未判定>0 或 不符合無發現 → 412
    BE->>PKG: finalize_assessment(有不符合→POA&M)
    U->>FE: 匯出報告 docx/pdf
    FE->>BE: GET /ar/export?format=docx
    BE-->>U: 1~6 章(AP)+7 章(attestation)+附件(findings)
圖 3 — 稽核次內從開始稽核到結案

§7

端到端驗收

  1. 新稽核次:DEV 從 FR-140 流程開一個新的內部稽核次,進稽核結果頁,左樹三層、全部燈號空心,DB 該 result finding 數為 0。
  2. 逐條判定:AC.L1-b.1.iv [a] 取消一份佐證、寫其他佐證與稽核發現、判不符合、風險等級高與改善建議,存檔重整還在;左樹 [a] 紅燈、控制項紅燈、族群紅燈;控制項總覽依據寫「評估目標 [a] 不符合」。
  3. 符合有風險:另一條判符合,按「新增風險」填風險等級中,所屬控制項變不符合。
  4. Excel 來回:匯出 Excel,改兩格稽核結果、加一個專案內檔名、寫一個不存在的檔名,匯回,預覽標出異動與「佐證檔案找不到」,選「改為其他佐證文字」後匯入,畫面更新。
  5. 第 7 章與報告:稽核報告頁籤按帶入不符合事項、修改、預覽報告:第 1 頁 1~7 章,第 2 頁橫式附件、判定中文、不符合列淡紅;下載 Word 用 Word 開確認。
  6. 結案:還有未判定時按結案被擋並列原因;補完後結案成功,頁面唯讀,再呼叫寫入 API 回 409;有不符合時 POA&M 有產生。
  7. OSCAL:匯出該 AR 的 OSCAL JSON,用 ~/.claude/skills/oscal-knowledge/references/json-schemas/oscal_assessment-results_schema.json 驗證通過;finding 的 target.status.state 為 satisfied/not-satisfied、risk 的 characterizations 為陣列、finding 的 related-risks 有值。
  8. 第 2 次內稽:從有不符合的稽核次開下一次內稽,新稽核計畫只剩不符合評估目標所屬控制項。

§8

拆分表

卡 做什麼 碰哪裡 套件 依賴 驗收
1a finding OSCAL 形狀、risk facets、匯出讀連結表、remediation lifecycle、清 pending token、migration pkg/jedi-oscal-v2:domain/service/ar/ar_finding_matrix_service.py(186 行)、app/service/ar/assessment_risk_service.py(168)、app/service/io/oscal_io_service.py(1,720,超標只准新開檔)、infra/model/ar/、migrations/013-*.sql jedi-oscal-v2 — 驗收 7
1b observation/attestation service、新稽核次不預建 finding、D5 規則收斂、匯出佐證凍結欄位 pkg/jedi-oscal-v2 新 service;pkg/jedi-compliance-audit/app/service/assessment_result_app_service.py(824,超標只准新開檔)、ar_import/aggregation.py 兩支 1a 驗收 1
2 ar_objective_app_service.py 與 API、結案守門+鎖定+凍結 app/flow_control/service/、api/project/routes/、common/code/ — 1b 驗收 2、3、6 的 API 部分
3 FE 新頁 FE src/views/project/、src/components/grc/ar-result/ — 2 驗收 2、3、6 畫面部分
4 Excel 結果欄、檔名比對(佐證欄只用換行切、比對過 ap_workbook_diff._norm)、預覽;匯出名「稽核結果工作表(Excel)」 app/flow_control/service/ap_workbook_*.py 擴充+新 ar_workbook_*;FE 預覽模式 — 1b(FE 部分等 3) 驗收 4
5 報告 Word/PDF app/oscal/templates/ap/ar_report_template.docx、app/flow_control/service/ar_* — 2 驗收 5
6 縮範圍改評估目標層、選單 seed pkg/jedi-compliance-audit/app/service/pass_scope_rule.py、scripts/sql/*-seed-*.sql jedi-compliance-audit 1b 驗收 8

§9

坑與陷阱

# 坑 影響 對策
1 init_ar_matrix_for_round 現在預建 finding 且 target_id 存 control_id(2026-06-17 拍板) FR-140 縮範圍、FE 聚合、FR-044 匯入都假設「每控制項一筆」 1b 改 AO 層時 grep target_id 全部讀取者(pass_scope_rule、aggregation、ar_import、FE)一次改;D4 用 _obj. 判新舊
2 target_status JSONB 現存 {"product_state"},匯出直接吐 改形狀後舊資料匯出仍不合規 1a migration 把舊值轉成 {state, reason};舊 pending 列刪除(=未判定)
3 finding↔︎risk 連結表與 JSONB 不同步 匯出遺失關聯 1a 匯出改讀連結表,JSONB 欄停用(不刪)
4 assessment_result_app_service.py 860 行、RoundAuditReviewView.vue 1,083 行、ArImportDialog.vue 720 行皆超標 不能往裡加 2a/3a/4a 全部新開檔;舊檔只改一行導向或退役
5 「有風險」判準=風險等級或風險說明任一有值 只填改善建議 → 不算風險、控制項不會變不符合 填寫說明明寫;匯入預覽標「有改善建議但無風險等級」提醒(不擋)
6 「帶入不符合事項」接在現有內容後 按兩次內容重複 實作同雛形:接在後面,不覆蓋
7 OSCAL status.state 封閉值域;「不適用」要同時填 satisfied+reason=other+implementation-status 漏任一匯出不合規或報告印錯 1a 寫在 upsert 一個地方;驗收用官方 JSON schema 跑
8 報告附件欄多 直式放不下 D11:附件節橫式、欄數 8;取消「含稽核發現與風險」為 7 欄
9 同一支 audit_round_app_service.py 周邊 FR-140 卡與本案 6a 都要改 平行派互撞 6a 排在 FR-140 相關卡之後,母卡標序列組
10 finding description 官方必填,但符合時稽核人員常不寫發現 匯出不合 schema 存檔時空白自動補「經檢視佐證,符合本評估目標」,畫面不顯示這句
11 佐證候選清單要走 workflow_execution_control_mapping 權威 scope 同名檔案跨控制項被吸進來(FR-044 STG 實踩) 依 memory feedback_evidence_per_control_scope_wf_control_mapping:二次過濾 workflow_execution_id 真的對到該控制項;relevant-evidence 存字串 file_uid 不存數字 file_id
12 結案凍結佐證版本 受稽單位結案後換檔,稽核紀錄指向新檔 結案時把每筆採用佐證的 content_hash/版本寫進 relevant-evidence.props
13 Excel 檔名比對同名多筆 兩個任務交了同名檔案 預覽就地讓使用者選,不自動挑