FR-142 · 設計定案 · 2026-10-16
左樹三層+控制項總覽、一次一條評估目標、稽核報告第 7 章、Excel 只關聯檔案、報告 Word/PDF
稽核階段判定頁整個換掉。左樹控制族群→控制項→評估目標三層;點控制項看控制項總覽,點評估目標依序填佐證、其他佐證、稽核發現、稽核結果、風險(只在不符合時出現);控制項結論由評估目標自動推算。「稽核報告」頁籤只寫第 7 章,預覽與匯出才組完整報告。Excel 是稽核計畫那份往後加結果欄,佐證只關聯檔案。結案整份鎖定、佐證凍結版本。資料照 OSCAL 評估結果標準欄位存,並修 jedi-oscal-v2 三處匯出不合規。本文件寫定案規則、為什麼這樣定、踩到會怎樣。
🔴 這份是設計決策,不是現況。 內文停在 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,皆 branchfeature/FR-142(自 FR-139b4048bd5e);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。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-16 | v1:D1~D12 定案;資料模型與 OSCAL 落點、API、畫面、Excel 契約、報告、六棒七張卡、端到端驗收 | 母卡 CM-2818 |
整案一句話:判定頁換成左樹到評估目標、中間一次一條評估目標(佐證→稽核發現→稽核結果→風險),控制項結論自動推算;稽核報告頁籤只寫第 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
D1 判定值域=符合/不符合/不適用三值,加「未判定」當初始狀態但不算判定
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。為什麼:顧問習慣一列填完;「先建風險再勾評估目標」是現在頁面難用的主因。大多數評估目標是符合,風險區收起畫面短一半。
被排除:維持「風險獨立建、再勾」為主路徑(難用);風險總覽頁籤與合併多筆(雛形第一版有,決策者看過後拿掉:風險已逐評估目標呈現、報告附件也逐列印,合併功能 Excel 無法來回)。
D3 「稽核報告」頁籤只寫第 7 章;1~6 章與附件只在預覽與匯出時組出完整報告
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 舊資料不遷移,只影響新開的稽核次
target_id 是否含 _obj.。RoundAuditReviewView.vue 退役後,既有控制項層稽核次用新頁唯讀顯示(每控制項一條合成卡),不開放編輯。為什麼:舊輪資料只有 DEV 與少數測試專案;把控制項判定拆到每條 AO 要猜,猜錯比不猜糟。FR-140 已有「每次稽核一筆 result」的分界點可借用。
D5 控制項結論不存欄位,由 AO 即時推導;三處各自實作收斂成套件一支
ar_import/aggregation.py 的 aggregate_verdict 擴充「掛風險」條件),FE 不再自己算、FR-140 縮範圍規則改呼叫它。為什麼:現在 FE(RoundAuditReviewView.vue:411)、BE 匯入(aggregation.py:10)、縮範圍(pass_scope_rule.py:12)三套規則各寫一次,「掛風險也算不符合」是新條件,三處改必漏。OSCAL AR 本來就沒有控制項層判定欄,存了反而要維護一致性。
D6 左樹到評估目標三層,點控制項看控制項總覽
為什麼:右側膠囊把評估目標與控制項說明擠到被截斷;中間一次只放一條評估目標,資訊才不會擠在一起;點控制項要看得到控制項結果。
被排除:膠囊放右側(擠);文字無限換行(樹太長);點控制項直接跳第一條評估目標(看不到控制項結果)。
D7 評估目標頁照稽核人員實際做事的順序:佐證 → 其他佐證 → 稽核發現 → 稽核結果 → 風險
為什麼:稽核人員是先看證據、寫下看到什麼、最後才判過不過;第一版把判定放最上面等於先下結論。備註框與「其他佐證」太像,使用者分不出來。
D8 佐證=這條評估目標對到的任務佐證(檔案、連結、問卷),一張平的清單,預設全部採用,取消勾選=不採用
public.workflow_execution_control_mapping 對到該控制項/評估目標的任務,底下 compliance.job_evidences 類型 FILE/LINK/SURVEY 的項目,合成一張清單、同筆去重;不分任務顯示,每列後面標上傳者與日期。job_execution_comments)與報告類型不列。為什麼:舊頁 RoundAuditReviewView 是「上面頁籤瀏覽、下面觀察表單另外多選關聯」兩塊;併成一張清單,看與勾在同一處。稽核人員不需要知道任務分組;討論是溝通紀錄、不是證據,有用的內容應寫進稽核發現。
被排除:自編證據池(第一版,未對應系統資料);依任務分組(看不出需要);討論可勾選採用(決策者裁:只供參考,後再裁拿掉);回到舊頁頁籤式(一條評估目標佐證通常三五筆,分頁籤要一直切)。
D9 Excel:與稽核計畫同一份四頁,計畫欄灰底唯讀,後接結果欄;佐證只關聯檔案
為什麼:稽核人員不會照「類型:名稱」格式寫;檔名是唯一實際會出現在 Excel 的證據形式,也是亞航附件的寫法。
D10 結案=本次稽核整份鎖定
為什麼:決策者原話「過了就過了,那不是結案報告,不然一直改等等會有爭議」。結案後產生 POA&M,若仍可改判定會與 POA&M 對不上。
D11 報告附件橫式 8 欄、判定印中文
為什麼:10 欄直式 A4 每欄不到 2 公分不可讀;合併兩組欄後與亞航原表寬度相近。
D12 表單文案用專業用語,不放說明句
座標以 2026-10-16 為準,runner 開工先開檔核一次。FR-141 已完整併入 feature/FR-139(git merge-base --is-ancestor 成立),其頁面骨架、六支 app/flow_control/service/ap_*.py、docx 範本都在主工作樹。
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 的舊檔」退路 |
| 元件 | 現況 | 本案動作 |
|---|---|---|
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' |
| 畫面欄位 | 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]
全部掛在既有 /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,畫面視為全部勾選;第一次存檔才寫入。
照雛形 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.)用新頁唯讀。
「評估目標」頁欄序:
| 欄 | 內容 | 規則 |
|---|---|---|
| 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 的列略過;全部處理完才能匯入。
GET /audit-round/<round_uid>/ar/export?format=docx|pdf|xlsx|zip&with_findings=true|false:
範本:複製 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。
pkg/jedi-compliance-audit/app/service/pass_scope_rule.py(28 行)carried_control_ids 改讀評估目標層 finding,套 D5 的同一支規則:控制項結論為不符合者帶入下一次稽核。舊稽核次(控制項層 finding)維持原邏輯。
%%{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)
~/.claude/skills/oscal-knowledge/references/json-schemas/oscal_assessment-results_schema.json 驗證通過;finding 的 target.status.state 為 satisfied/not-satisfied、risk 的 characterizations 為陣列、finding 的 related-risks 有值。| 卡 | 做什麼 | 碰哪裡 | 套件 | 依賴 | 驗收 |
|---|---|---|---|---|---|
| 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 |
| # | 坑 | 影響 | 對策 |
|---|---|---|---|
| 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 檔名比對同名多筆 | 兩個任務交了同名檔案 | 預覽就地讓使用者選,不自動挑 |