FR-140 · 設計定案 · 2026-10-15

一輪一條流程:稽核次、原卡退回、覆核輪保留

D1~D6、P1~P6 全部定案;六塊設計、十二張卡

一個稽核輪次走完整條流程:專案規劃與執行 → 內部稽核 → 有缺失就改善、把不合格的原任務卡退回原指派人重做、再內稽一次 → 內部沒缺失才進外部稽核 → 外部有缺失走既有的覆核輪。每一次稽核(第 1 次內稽、第 2 次內稽、外稽)各留一組 SSP 快照、評估計畫、評估結果、改善計畫,互不覆蓋。做法是把輪次上四個單值欄位改成一輪多筆的「稽核次」,引擎在流程第二次走到同一站時開新任務卡,任務卡加完成期限與到期提醒。本文件寫定案規則、為什麼這樣定、踩到會怎樣。

D1~D6、P1~P6 全部定案(2026-10-15) 六塊設計、十二張卡,範本編輯器先出畫面 動三支套件:jedi-compliance-audit、jedi-flow-engine、jedi-task-platform 陷阱:推進邏輯兩份、四槽位讀取者十餘處,漏一處靜默讀錯 退卡找卡的 control_id 對應實作前先用 DEV 資料驗

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

狀態:設計定案(2026-10-15),待開卡|母卡 CM-2765|討論稿:discussion.md

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

名詞:輪次=稽核輪次(compliance.project_audit_rounds),一輪對應一次專案稽核流程;稽核次=一輪內每做一次稽核(內部或外部)就是一筆(compliance.audit_round_passes);AP=評估計畫(assessment plan);AR=評估結果(assessment results);POA&M=缺失改善計畫(plan of action and milestones);job=流程引擎依範本站產生的任務卡執行紀錄(compliance.job_executions);站=範本(BPMN 流程圖)上的一個使用者任務節點;reverse 連線=範本上標記為「往回走」的連線;wecm=任務卡對控制項的對應表(public.workflow_execution_control_mapping)。

§1

變更紀錄

日期 變更 對應
2026-10-15 v1:D1~D6、P1~P6 設計定案;稽核次資料模型、輪次狀態機改綁、範本稽核性質、引擎回流開新卡、退卡與期限、覆核輪期限承襲六塊設計;十二張卡拆分與端到端驗收 母卡 CM-2765
2026-10-16 實作與複測後三條規則被推翻,正文不改寫、只在對應處標註:P6 進度條改「七站全畫、顏色只講這一次」;退回改沿實際軌跡、可跨稽核次;回規劃後到下一次計畫站前,三處顯示用推算補上這一次。理由見 docs/analysis/2026-10-16-fr140-progress-and-rollback-redesign.md 母卡 CM-2765

§2

分工概述(30 秒版)

整案一句話:一輪就是一條完整流程,內稽不合格就把那幾張原任務卡退回重做、再稽核一次,內稽過了才進外稽;每一次稽核各留一組 SSP 快照+AP+AR+POA&M,看得出是內部或外部、第幾次。

設計塊 做什麼 產出 完成怎麼判定(決策者親手做什麼、看到什麼)
一、稽核次表 把輪次上四個單值槽位(SSP、AP、AR、POA&M)改成一輪多筆 compliance.audit_round_passes 表、輪次加 current_pass_id、既有輪次搬成第 1 次的 migration 在 DEV 查一個既有輪次,看到它有一筆第 1 次稽核次、四個值與輪次上的一致;用既有範本跑一輪,畫面行為與現在相同
二、輪次狀態機改綁稽核次 四個站方法的斷言與寫入改到「目前稽核次」;每次走到稽核計畫填寫站開一筆新的稽核次 audit_round_app_service.py 改寫、finalize_audit 加 terminal 參數、輪次 status 與目前稽核次同步 跑完第 1 次內稽有缺失、退回、再跑第 2 次內稽;查 DB 看到兩筆稽核次、各自的 AP/AR/POA&M 不同,第 1 次的資料沒被蓋掉
三、範本 站多一個「稽核性質」(內部/外部)下拉;新公版「內外稽核流程」範本 system_menus 的 audit_nature 選項、範本編輯器下拉、新公版 BPMN seed 打開範本編輯器,點稽核計畫填寫站看到「稽核性質」下拉可選內部/外部;公版清單多一個「內外稽核流程」,打開看到兩段稽核與一條往回走的虛線
四、引擎回流開新卡 流程第二次走到同一站時新建一筆 job,不再重用第一次那筆 兩份 complete_*_job 改寫、attempt_no 欄、依站 element id 找 job 退回後重做的卡在任務歷程看得到兩筆,第一次的完成時間還在
五、退卡與期限 內稽不合格的控制項找到原任務卡退回原指派人、壓上 POA&M 里程碑日期當期限、留重做紀錄;到期提醒 job_executions.due_at 全鏈、退卡 handler、job_execution_redo_log 表、job_due 提醒 內稽標 3 條不合格並填好改善計畫里程碑,完成改善站後,原指派人的「我的任務」出現 3 張退回卡、期限=里程碑日期、收到通知;離到期 3 天時收到提醒信
六、覆核輪期限承襲 外稽不合格開覆核輪時,新卡帶期限 _generate_reverify_prep_jobs 寫 due_at 外稽標不合格並填里程碑、啟動覆核輪,覆核輪的新卡期限=外稽改善計畫的里程碑日期

依賴:一→二→(四、五)是主線;三與五的期限欄(BE+FE)可平行先做;最先看得到畫面的是第三塊的範本編輯器下拉(卡 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
    subgraph G1[地基]
      K1[卡1 稽核次表<br/>+migration]
      K2[卡2 稽核性質選單<br/>+公版範本+編輯器下拉]
      K6[卡6 期限欄<br/>BE 全鏈]
    end
    subgraph G2[主線]
      K3[卡3 開新稽核次<br/>launch_audit 改綁]
      K4[卡4 其餘三站改綁<br/>+terminal+回規劃]
      K5[卡5 引擎回流開新 job]
      K8[卡8 回流退卡 handler]
      K7[卡7 FE 期限欄]
    end
    subgraph G3[收尾]
      K9[卡9 到期提醒]
      K10[卡10 總覽稽核次切換<br/>+標題帶第 N 次]
      K11[卡11 覆核輪期限承襲]
      K12[卡12 流程進度條]
    end
    K1 --> K3 --> K4
    K4 --> K5
    K4 --> K8
    K5 --> K8
    K6 --> K7
    K6 --> K8
    K2 --> K3
    K8 --> K9
    K4 --> K10
    K6 --> K11
    K5 --> K12
圖 1 — 十二張卡的依賴(箭頭=必須先做)

§3

需求與端到端流程

%%{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 TD
    plan["專案規劃+執行"] --> iap["內部稽核計畫"]
    iap --> iaudit["內部稽核"]
    iaudit --> g1{"有缺失?"}
    g1 -- 有 --> ipoam["內部缺失改善(POA&M)"]
    ipoam -. 退回 .-> plan
    g1 -- 無 --> eap["外部稽核計畫(全範圍)"]
    eap --> eaudit["外部稽核"]
    eaudit --> g2{"有缺失?"}
    g2 -- 無 --> done(["結案"])
    g2 -- 有 --> epoam["外部 POA&M"]
    epoam --> endr(["本輪結束(待覆核)"])
    endr -. 發起覆核 .-> rev["覆核輪(現有機制,新的一輪)"]
圖 2 — 決策者確認的一輪流程(虛線=退回)

逐句需求(決策者 2026-10-15 確認):

  1. 一輪=一條完整流程,內稽與外稽都在同一輪內(PM 裁決)。
  2. 內部稽核有缺失 → 做內部缺失改善(POA&M)→ 退回專案規劃+執行(圖上虛線)。
  3. 退回時,不合格的任務卡原卡退回起點,原指派人收到通知重做;卡有完成期限=該控制項 POA&M 的里程碑日期。
  4. 重做完再走一次內部稽核計畫 → 內部稽核(第 2 次),計畫只列上次不合格的控制項。
  5. 內部稽核無缺失 → 外部稽核計畫(全範圍)→ 外部稽核。
  6. 外部稽核有缺失 → 外部 POA&M → 啟動覆核輪(現有機制:新的一輪、新卡承襲、只建缺失項);覆核輪任務卡的期限=外稽 POA&M 的里程碑日期。外部稽核無缺失 → 結案。
  7. 每一次稽核各留一組 SSP 快照+AP+AR+POA&M,看得出是內部或外部、第幾次,不被下一次覆蓋。
  8. 專案管理頁永遠看全部控制項;內稽回流只是其中幾張卡退回。覆核輪才是只有缺失項的小圖。

內稽回流與覆核輪看起來都是「不合格再做一次」,但是兩件事:

比較 內稽回流(本案新做) 覆核輪(既有機制,不動)
發生在哪 同一輪內,流程退回規劃站 新開一輪
任務卡 原卡退回原指派人 新卡,承襲母輪資料
PM 看到的範圍 全部控制項,其中幾張卡變回待處理 只有缺失項
下一次的 AP 從上一次內稽 AP 縮到不合格項 只建缺失項

§4

決策定案 D1~D6、P1~P6

決策者 2026-10-15 全部裁定。

項 定案 理由 被排除的方案
D1 不合格怎麼重做 內稽不合格的任務卡原卡退回:status 回「進行中」(沿用既有退回語意,指派人不必再按開始)、end_time 清空,指派人、部門、設備、證據都不動;另寫一筆重做紀錄保留歷次完成時間。外稽不合格走覆核輪、開新卡 內稽回流是同一輪內的修正,PM 要在同一張全控制項的圖上看到哪幾張卡退回;原卡退回讓指派人在原地補做,證據與指派關係不必重建。外稽是對外的正式結果,維持既有覆核輪 內稽也開新卡(證據與指派要重建,PM 頁會出現重複卡);外稽也原卡退回(決策者明示外稽不過維持開新輪)
D2 第 N 次的 AP 範圍 第 N 次(N≥2)內稽的 AP 從上一次內稽 AP 複製、縮到 not_met 控制項,沿用覆核輪的 clone 邏輯、來源改成同一輪上一次稽核次;外稽 AP 一律全範圍新建 第 2 次內稽只是確認上次不合格的項目改好了,不必重查全部;外稽是正式稽核,必須涵蓋全範圍;clone 邏輯已在覆核輪驗證過,換來源即可重用 每次內稽都全範圍(重查已合格項目,徒增工作);另寫一套縮範圍邏輯(與覆核輪重複)
D3 怎麼留下每一次稽核 新增稽核次表,一輪多筆,每筆帶 SSP 快照、AP、AR、POA&M;概念同 living SSP:活本是任務卡與證據,每次走到「稽核計畫填寫」站凍一組 輪次上四個槽位是單值,第二次稽核必然覆寫第一次;需求要求每一次都留得下來且分得出內外與次數,只能改成多筆 維持四個單值槽位(第二次蓋掉第一次);每次稽核開一個新輪(違反「一輪=一條流程」,PM 頁會拆成多輪)
D4 引擎改多少 引擎 token 走法與範本驗證規則都不動;只改「回到同一站時新開一筆 job」與站 handler 的綁定 範本已畫得出退回再稽核的圖(同站碼可重複、reverse 與閘道都跳過檢查),問題只在 job 重用與輪次單向,改動收在這兩處最小 改引擎走法或驗證規則(動到所有既有範本的行為)
D5 覆核輪 照現有方式開新輪、新卡承襲、只建缺失項;唯一新增是新卡帶上期限 外稽不過維持開新輪是決策者明示;期限是本案新加的欄位,覆核輪的卡也要有才一致 把覆核輪改成輪內回流(決策者明示不做)
D6 舊資料與舊範本 migration 把既有輪次的四槽位搬成第 1 次稽核次;舊範本一輪一次照常跑 既有輪次與範本不能因本案失效;搬成第 1 次後,所有讀取者統一讀「目前稽核次」,不必分新舊兩條路 新舊兩條讀取路徑並存(每個讀取者都要判斷新舊,漏一處就壞)
P1 記不記重做來源 記:稽核次帶 prev_pass_id,第 2 次內稽指向第 1 次 畫面串得出「第 1 次 → 第 2 次」的軌跡,不必用序號推算 不記,靠 audit_nature+nature_seq 推算
P2 輪次自己的 status 保留欄位與七態,跟著目前稽核次同步寫;斷言改綁稽核次 讀輪次 status 的地方很多,保留七態舊畫面不會壞 收成粗狀態(進行中/待覆核/結案)——所有讀者都要改
P3 第 2 次內稽無缺失時輪次怎麼不被結案 finalize_audit 加參數 terminal,由 handler 依「這站之後是否接結束事件」決定;terminal=true 才把輪次設 closed 舊範本(稽核站後直接接結束)行為不變;新範本內稽後還有外稽時不會被提早結案 一律不設 closed、改由範本結束事件結案(舊範本行為跟著變)
P4 到期提醒 到期前 3 天、到期當天兩個時點,寄指派人+PM;沿用 task_alert_notifications,object_type 新增 job_due,stage 為 due_3d/due_today 兩個時點足以提醒又不擾人;沿用既有提醒表與排程,不另立機制 前 7 天、前 1 天、逾期當天三個時點;只在逾期當天寄指派人
P5 期限可否手改 PM 可手改,改了標「手動調整」,不回寫 POA&M 兩邊責任分清楚:POA&M 是改善計畫,任務卡期限是執行安排 不可改、只能去改 POA&M(PM 調排程要繞一圈)
P6 進度怎麼顯示(⚠ 進度條規則已推翻,見變更紀錄 2026-10-16) 總覽頁輪次下拉底下多一層「第 N 次內部/外部」切換,另畫一條照範本站排列的進度條;進度條直接讀引擎目前所在的站,不推算 一輪內有多次稽核,沒有切換與進度條看不出現在走到哪;讀引擎目前站才不會與實際狀態漂移 只加切換、不畫進度條;進度條依輪次 status 推算(回流時會推錯)

§5

核心概念:稽核次

稽核次的想法與既有的 living SSP 相同:有一份一直在變的「活本」,每到某個時點就凍一份下來。

Living SSP 怎麼運作

專案有一份持續編輯的 SSP(活本)。啟動稽核時把它凍成一份快照,稽核只看快照;活本繼續改,不影響正在進行的稽核。

稽核次怎麼運作

活本是任務卡與證據:指派人一直在上面做事。每次流程走到「稽核計畫填寫」站,就凍一組 SSP 快照+AP+AR+POA&M 成為一筆稽核次。第 2 次內稽、外稽各自再凍一組,前面的不會被覆蓋。

比較 Living SSP 稽核次
活本 專案的 SSP 任務卡與證據
凍結時機 啟動稽核 每次走到「稽核計畫填寫」站
凍結內容 一份 SSP SSP 快照+AP+AR+POA&M 一組
掛在哪 輪次的 ssp_id 稽核次表,一輪多筆
第 N 次怎麼來 — 內稽第 N 次(N≥2)的 AP 從上一次內稽複製、縮到不合格項;外稽 AP 一律全範圍新建

§6

現況接入點

每個接入點列現況與本案動作,座標以 2026-10-15 為準。

① 流程引擎(兩份推進邏輯,都要改)

接入點 現況 本案動作
建 job pkg/jedi-flow-engine/app/service/workflow_execution_service.py:308-335;主專案版 app/flow_engine/service/workflow_execution_service.py:612-640(complete_main_workflow_job 從 :512 起)。查重 key=(workflow_execution_id, template_id, template_job_id),查到就重用並改回 PROCESSING(主專案版 :625-629 覆寫 start_time、不清 end_time)。同一站第二次走到不會建新筆,第一次的紀錄被蓋掉 遇到同站已 COMPLETED 時新建一筆、加 attempt_no(第四塊)
COMPLETED 判定 套件 :285-292;主專案 :590-609(多排除 CANCEL) 兩份一致改成「看最新一筆」
退回 job 主專案 workflow_execution_service.py:813 revert_job 已有::832 輪次凍結檢查;:882-884 目標改 PROCESSING、清 end_time;:887-889 notify_user_todo_job 通知;:910-912 目前 job 回 TODO、後續 job 全回 TODO。套件版 :339-422 沒有通知 不整段呼叫(它會把目標之後所有 job 回 TODO,只退幾張時會波及其他卡);把「單卡回 PROCESSING、清 end_time、通知」抽成單卡函式(新檔),回流 handler 逐張呼叫(第五塊)
job 對控制項 public.workflow_execution_control_mapping(wecm):control_id varchar(50) 見 scripts/sql/2026-06-15-fr038-q1-workflow-execution-control-mapping.sql:18-21、ao_part_id 見同檔 :35;round_id 見 2026-07-22-fr053-wecm-round-id.sql:16 退卡時用 (control_id, ao_part_id, round_id) 找卡

② 範本驗證與站屬性

接入點 現況 本案動作
同一站碼出現兩次 pkg/.../common/utils/bpmn_topology_validator.py:82-102、:195-212 只檢查站碼存在,不擋重複 不改;選配加「同站碼多次必帶 audit_nature」
allowed_successors 檢查 同檔 :232 只查使用者任務直連使用者任務;目標是閘道或結束事件就跳過(:225-228) 不改
reverse 連線 sequenceFlow 帶 camunda:property name="reverse" value="true"(:138-154,判斷在 :144) 新公版範本的「內部缺失改善 → 規劃」必須標 reverse
站之間允許的下一站 scripts/sql/2026-09-14-fr093-4d-flow-templates-stage-objects.sql:194/201/222/229:planning→ap_authoring、ap_authoring→audit、audit→poam、poam→[] 不改(閘道與 reverse 都會被跳過檢查)
閘道條件 字串 ${has_findings == true}(scripts/sql/seeds/bpmn/builtin-full-audit-with-review.bpmn:91);組字串 bpmn_uilts.py:414-437;比對是字串全等(:322-327) 新範本照抄同一字串
站屬性讀取 現有 stage_object_code、main_role;parser 把所有 camunda:property 讀進 JobDTO.properties(bpmn_uilts.py:70-76),加 audit_nature 不用改 parser 新增 audit_nature 屬性
站屬性消費端 stage_advance_service.py:115,251,497,586-587,648;stage_rollback_service.py:181-187 的 _find_job_by_stage_code 只回第一筆 同站碼出現兩次會找錯站,改用 element id
範本編輯器 FE src/views/flow-template/FlowTemplateEditorView.vue:userTaskForm :244、讀屬性 :278-280(helper :311-319)、寫屬性 :322-360、面板 :1093-1126、驗證 :592-605 面板加「稽核性質」下拉

③ 輪次狀態機與四槽位

站 handler(app/flow_control/service/oscal_stage_handlers.py:8-11,42-106)呼叫 pkg/jedi-compliance-audit/app/service/audit_round_app_service.py:

站 方法 斷言 推進後
planning launch_audit :413 status ≠ planning 就擋 轉 audit_planning(:438);:435 寫 assessment_plan_id、:437 寫 ssp_id,直接覆寫
ap_authoring start_auditing :474 ≠ audit_planning 就擋;:476 沒有 AP 擋 轉 auditing(:487);:481 只在 not ar_result_id 時建 AR,第二次會沿用舊 AR
audit finalize_audit :506 ≠ auditing 就擋 沒有不合格→closed(:513);有→remediation(:520),:521 寫 poam_id
poam close_round :582 ≠ remediation 就擋;:569 POA&M 全關才可 母輪轉 pending_reverify(:584)

閘道判斷 has_findings:stage_advance_service.py:614-628,等於 handler_result.status == "remediation";finalize_audit 讀 assessment_result_app_service.py:823-845(:830 讀 round.ar_result_id)。

四槽位(ssp_id、assessment_plan_id、ar_result_id、poam_id)的讀取者,全部要改讀「目前稽核次」:

repo 位置
主專案 oscal_stage_preconditions.py:53,57;assessment_plan_app_service.py:204,222,526,528;app/oscal/service/ssp_control_round_scope.py:10,49-66,76,91,93;ssp_control_implementation_service.py:285,290,298,304;stage_advance_service.py:558;oscal_stage_rollback_handlers.py:39
套件 audit_round_app_service.py:189-203,634-637;assessment_result_app_service.py:9,208,360-379,748,750,830;poam_app_service.py:422,424,443,450;infra/repository/flow_control_task_setup_repo_impl.py:33;project_audit_round_repo_impl.py:50

覆核輪:launch_reverify(audit_round_app_service.py:596)clone AP 並縮到 not_met、_generate_reverify_prep_jobs(:732)產卡、ReverifyInheritanceService 承襲;承襲查詢 infra/flow_control/repository/reverify_clone_query.py:31;DEV 範例 project 1321。

④ 任務卡與期限

接入點 現況 本案動作
job 欄位 compliance.job_executions 只有 start_time/end_time/next_due_at 加 due_at
ORM/entity/mapper/repo pkg/jedi-flow-engine/infra/models/job_execution.py:16-47;domain/entity/job_execution_entity.py:34;mapper :34;repo update 只寫 status 與時間 全鏈補 due_at
task-platform entity pkg/jedi-task-platform/.../flow_control_job_entity.py:126 補 due_at
詳情 API api/flow_control/routes/job_route.py:75-94 → job_service.py:178 → job_dto.py:226-265 → serializers/job.py:166-183 回傳 due_at 與「手動調整」標記
我的任務 view scripts/sql/view/vw_user_job_queue.sql:62-63 → my_grc_jobs_query.py:96 view 加欄(重建要 security_invoker)
提醒排程 core/scheduler.py:622-646;task_alert_notifications 的 object_type CHECK(2026-10-13-fr137-task-alert-notifications-object.sql:30-36) 新增 job_due,CHECK 放寬
POA&M 里程碑 oscal.poam_milestones.target_date;套件 poam_app_service.py _item_control_id(:183)、_deadline(:212) 期限取該控制項最早的 target_date
FE 頁面 MyTasksView.vue(751 行)、JobExecutionDrawer.vue(2393 行,已超標) 新邏輯開 composable/子元件新檔

🔴 真正的牆在輪次層,不在引擎

範本不擋:同一站碼可以出現兩次、reverse 連線與閘道都會被跳過檢查,畫得出「退回再稽核一次」的圖。

引擎會重用 job:同一站第二次走到時拿第一次那筆改回進行中,第一次的完成紀錄被蓋掉——這是第四塊要改的。

輪次是單向的:每個站方法都斷言「目前狀態必須是上一階段」,而且 SSP、AP、AR、POA&M 只有四個單值槽位,第二次稽核會直接覆寫第一次。這是本案的核心,也是第一、二塊要改的。


§7

詳細設計

資料模型

新表:稽核次 compliance.audit_round_passes

欄位 型別 說明
id bigserial PK
uid uuid 對外識別
round_id FK → project_audit_rounds.id 屬於哪一輪
pass_no int 輪內序號,1 起算
audit_nature varchar internal/external,值來自 system_menus group audit_nature;舊資料可為 NULL(畫面顯示「未標示」)
nature_seq int 同性質第幾次(第 2 次內部、第 1 次外部)
prev_pass_id FK → 本表,可 NULL 這次是哪一次的重做(P1):第 N 次內稽(N≥2)指向同一輪上一次內稽,其餘為 NULL
status varchar planning/audit_planning/auditing/remediation/closed,沿用 RoundStatus 子集
ssp_id 這次凍結的 SSP 快照
assessment_plan_id 這次的 AP
ar_result_id 這次的 AR result
poam_id 這次的 POA&M
template_job_id 對應範本上的 BPMN element
created_user/created_time/updated_user/updated_time 審計欄

GRANT ... TO cm_app(含 sequence 權限)。

既有表加欄

表 欄位 說明
compliance.project_audit_rounds current_pass_id 指向目前稽核次;輪次上四個槽位與 status 保留,跟著目前稽核次同步寫(P2)
compliance.job_executions due_at timestamptz 任務卡完成期限
compliance.job_executions attempt_no 同一站第幾次走到(第四塊)

新表:重做紀錄 compliance.job_execution_redo_log

欄位 說明
job_execution_id 被退回的卡
pass_id 因哪一次稽核不合格而退回
prev_end_time 退回前的完成時間(退回會清 end_time,靠這欄保留歷次完成時間)
revert_at 退回時間

既有輪次的搬移規則見「一、稽核次表」。

%%{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'}}}%%
erDiagram
    project_audit_rounds ||--o{ audit_round_passes : "一輪多次"
    project_audit_rounds |o--o| audit_round_passes : "current_pass_id"
    audit_round_passes |o--o| audit_round_passes : "prev_pass_id"
    audit_round_passes }o--|| ssp_snapshot : "ssp_id"
    audit_round_passes }o--o| assessment_plan : "assessment_plan_id"
    audit_round_passes }o--o| ar_result : "ar_result_id"
    audit_round_passes }o--o| poam : "poam_id"
    job_executions ||--o{ job_execution_redo_log : "歷次退回"
    audit_round_passes ||--o{ job_execution_redo_log : "因這次退回"
圖 3 — 稽核次與相關表

端到端時序

以「第 1 次內稽有 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'}}}%%
sequenceDiagram
    participant PM as PM
    participant U as 指派人
    participant A as 稽核員
    participant E as 流程引擎
    participant R as 輪次服務
    participant P as 稽核次
    PM->>E: 啟動專案,產出規劃+執行任務卡
    U->>E: 完成任務卡
    E->>R: 走到內部稽核計畫填寫站
    R->>P: 開新稽核次,拍 SSP 快照、建 AP
    Note over P: 開新稽核次 #1 內部
    A->>E: 填完 AP 送出
    E->>R: start_auditing
    R->>P: 在 #1 建新 AR result
    A->>E: 內部稽核完成,3 條不合格
    E->>R: finalize_audit
    R->>P: #1 轉 remediation,寫 poam_id
    PM->>E: 內部缺失改善完成,POA&M 全關
    E->>R: close_round,下一條是 reverse 回規劃
    R->>P: #1 closed,輪次回 planning
    R->>E: revert_job 退回不合格卡,寫 due_at 與重做紀錄
    Note over U,E: 退 3 張卡,期限=里程碑
    E->>U: 通知重做
    U->>E: 重做完成
    E->>R: 再走到內部稽核計畫填寫站
    R->>P: 開新稽核次,複製 #1 AP 縮到不合格項
    Note over P: 開稽核次 #2 內部,AP 縮到不合格項
    A->>E: 第 2 次內稽完成,無缺失
    E->>R: finalize_audit,terminal=false
    R->>P: #2 closed,輪次不結案
    E->>R: 走到外部稽核計畫填寫站
    R->>P: 開新稽核次,AP 全範圍新建
    Note over P: 開稽核次 #3 外部,AP 全範圍
    A->>E: 外部稽核完成,有缺失
    E->>R: finalize_audit
    R->>P: #3 轉 remediation
    PM->>E: 外部 POA&M 完成
    E->>R: close_round,下一條接結束
    R->>R: 母輪轉 pending_reverify
    PM->>R: 啟動覆核輪(既有機制)
    R->>E: 新輪新卡,due_at=外稽里程碑
圖 4 — 一輪走完內稽回流與外稽

每站 handler 做什麼:

站 觸發的輪次方法 對稽核次做什麼 對任務卡做什麼
規劃+執行完成,進到稽核計畫填寫站 先開新稽核次,再 launch_audit 新建一筆(讀站屬性 audit_nature);拍 SSP 快照;內稽第 N 次(N≥2)複製上一次 AP 縮到 not_met,其他新建 AP;狀態 planning → audit_planning 無
稽核計畫填寫完成(內/外) start_auditing 每次都新建 AR result(同一個 AR 根底下追加,標題帶「第 N 次內部/外部」);狀態 → auditing 無
稽核完成、有缺失 finalize_audit 狀態 → remediation,寫 poam_id;閘道走「有」 無
稽核完成、無缺失、下一站是外部稽核計畫 finalize_audit(terminal=false),接著開新稽核次+launch_audit 這次 closed、輪次不結案;新開一筆外部稽核次,AP 全範圍 無
稽核完成、無缺失、下一個是結束事件 finalize_audit(terminal=true) 這次 closed、輪次 closed 無
內部缺失改善完成(下一條是 reverse 回規劃) close_round 這次 closed,輪次 status 回 planning(下一筆要到計畫站才建;這段期間畫面用推算補上,見變更紀錄 2026-10-16) 篩 not_met 控制項 → 找原卡 → 退回、寫期限、寫重做紀錄、通知
外部缺失改善完成(下一個是結束事件) close_round(維持現有) 這次 closed,母輪 → pending_reverify 無(覆核輪產卡時再寫期限)
啟動覆核輪 launch_reverify(不動) 新的一輪 新卡承襲,due_at=外稽 POA&M 里程碑

六塊設計

每塊寫改哪些檔(座標)、規則、陷阱。

一、稽核次表

改哪些檔:新 migration(scripts/sql/,建 compliance.audit_round_passes、project_audit_rounds 加 current_pass_id、搬舊資料);pkg/jedi-compliance-audit 的 entity、ORM、repo(project_audit_round_repo_impl.py 旁新增 pass repo)。

規則:

  • 欄位照上方「資料模型」;audit_nature 值來自 system_menus group audit_nature,不寫死在程式。
  • 既有輪次各建 pass_no=1,audit_nature 取範本站屬性,沒有就 NULL(畫面顯示「未標示」);四槽位複製;current_pass_id 指向它。
  • 新表 GRANT ... TO cm_app、sequence 權限;收尾 INSERT public.schema_migrations。

陷阱:本 migration 是主線 schema 變更,寫完要回報出貨基線待重產;升級既有客戶資料時第 1 次稽核次必須補齊,否則所有改讀目前稽核次的地方都會讀到空值。 狀態對應:輪次 pending_reverify → 稽核次 closed、not_started → planning、其餘同名;輪次自己的 status 不動。

二、輪次狀態機改綁稽核次

改哪些檔:pkg/jedi-compliance-audit/app/service/audit_round_app_service.py(launch_audit、start_auditing、finalize_audit、close_round);app/flow_control/service/oscal_stage_handlers.py;四槽位讀取者全部(見「現況接入點」③的清單)。

規則:

  • 四個方法的斷言與寫入改到目前稽核次;輪次的 status 欄位與七態保留,每次寫稽核次狀態時同步寫輪次 status,四個槽位同步寫目前稽核次的值(P2)。
  • 走到稽核計畫填寫站時,handler 先「開新稽核次」:讀站屬性 audit_nature;內稽第 N 次(N≥2)複製上一次內稽 AP 縮到 not_met;外稽全新 AP;每次重拍 SSP 快照;重做的稽核次一律寫 prev_pass_id 指向同一輪上一次內稽(P1);nature_seq 依同性質已有筆數加一;寫完把輪次 current_pass_id 指向新筆。
  • start_auditing 每次新建 AR result,在同一個 engagement 的 AR 根底下追加,標題帶「第 N 次內部/外部」。
  • finalize_audit 加 terminal 參數(P3):handler 看範本,這站之後經閘道「無缺失」那條接的是結束事件就傳 terminal=true(稽核次與輪次都 closed),接的是下一個稽核計畫填寫站就傳 false(只有稽核次 closed,輪次不結案、接著開新稽核次)。舊範本稽核站後直接接結束,行為不變。
  • close_round:下一條是 reverse 回規劃時,這次設 closed、輪次 status 回 planning、觸發退卡;下一個是結束事件時維持現有(母輪 → pending_reverify 供覆核)。
  • AR 是「每一次稽核各一份」:同一次內稽核計畫退回再送出沿用原 AR(判定不丟),新的一次才建新 AR;外稽的稽核次在內稽無缺失定案那一刻開(中間沒有規劃站);回規劃以「下一站是規劃」判斷。

陷阱:

  • start_auditing 的 not ar_result_id 判斷(:481)會讓第二次沿用第一次的 AR,必須改成每次新建。
  • launch_audit 直接覆寫 ssp_id/assessment_plan_id(:435、:437),改綁稽核次後才不會蓋掉第一次。
  • 四槽位讀取者清單要逐一改讀目前稽核次,漏一處就會讀到第一次的資料而不報錯。

三、範本

改哪些檔:system_menus seed migration(group audit_nature:internal、external);FE FlowTemplateEditorView.vue(面板 :1093-1126 加下拉、讀寫屬性 :278-280、:322-360);新公版 BPMN seed(scripts/sql/seeds/bpmn/);選配 bpmn_topology_validator.py。

規則:

  • 站屬性 audit_nature 用下拉,選項讀 system_menus。
  • 新公版「內外稽核流程」:規劃 → 內部稽核計畫填寫 → 內部稽核 → 閘道 → 內部缺失改善 →(reverse)回規劃;閘道無缺失 → 外部稽核計畫填寫 → 外部稽核 → 閘道 → 外部缺失改善 → 結束;閘道無缺失 → 結束。
  • stage_objects.allowed_successors 不用改(閘道與 reverse 都會被跳過檢查)。
  • validator 選配規則:同一站碼出現多次時,每一站都必須帶 audit_nature。

陷阱:

  • 內部缺失改善 → 規劃那條連線必須標 reverse,否則 allowed_successors 檢查會擋(poam 的下一站清單是空的)。
  • 閘道條件字串必須與既有一字不差:${has_findings == true},比對是字串全等。

四、引擎回流開新卡

改哪些檔:pkg/jedi-flow-engine/app/service/workflow_execution_service.py:308-335 與主專案 app/flow_engine/service/workflow_execution_service.py:612-640;stage_rollback_service.py:181-187;job_executions ORM/entity(attempt_no)。

規則:

  • 兩份 complete_*_job 遇到同 element 已 COMPLETED 時新建一筆,attempt_no 遞增。
  • get_job_execution 取最新一筆。
  • _find_job_by_stage_code 改用 element id 找。

陷阱:

  • 推進邏輯有兩份(套件與主專案),兩份都要改,只改一份會出現行為分歧。
  • _find_job_by_stage_code 只回第一筆,新公版範本同一站碼出現兩次(內部與外部稽核計畫填寫),不改會找錯站。

五、退卡與期限

改哪些檔:job_executions.due_at 全鏈——pkg/jedi-flow-engine ORM(infra/models/job_execution.py)、entity、mapper、repo update;pkg/jedi-task-platform flow_control_job_entity.py;主專案 job_dto.py、serializers/job.py、vw_user_job_queue.sql、my_grc_jobs_query.py;FE MyTasksView.vue、JobExecutionDrawer.vue(新邏輯開新檔)。退卡 handler 新檔;compliance.job_execution_redo_log migration;core/scheduler.py 提醒;task_alert_notifications CHECK migration。

規則:

  • 回流 handler:取目前稽核次 AR 的 not_met 控制項 → 經 wecm(control_id、ao_part_id、round_id)找卡 → 逐張呼叫從 revert_job 抽出的單卡退回函式(略過輪次凍結檢查、保留通知;不呼叫整段 revert_job,否則後續所有 job 會被一起退回)→ due_at 寫該控制項 POA&M 最早的 target_date(經 job_due_date_service.due_at_from_milestone_date 換成台北當天 23:59:59,與覆核輪同一把尺)→ 寫一筆重做紀錄(job_execution_id、pass_id、prev_end_time、revert_at)。
  • 提醒 job_due 兩個時點:到期前 3 天、到期當天,寄指派人+PM;寫入 task_alert_notifications,object_type job_due,stage due_3d/due_today(P4)。
  • PM 可手改期限,改了標「手動調整」,不回寫 POA&M(P5);詳情 API 回傳 due_at 與手動調整標記。

陷阱:

  • repo update 只寫 status 與時間,due_at 要補進 update 欄位,否則寫了不會存。
  • view 重建要帶 security_invoker,否則繞過 RLS。
  • task_alert_notifications 的 object_type CHECK 要放寬加 job_due,否則寫入直接被擋。
  • wecm 的 control_id 格式與 AR finding 的 target_id 能不能直接對上尚未驗證,實作前要先拿 DEV 資料比對。
  • MyTasksView.vue、JobExecutionDrawer.vue 只准新開 composable/子元件,不准往原檔加邏輯(JobExecutionDrawer.vue 2393 行已超標)。

六、覆核輪

改哪些檔:pkg/jedi-compliance-audit audit_round_app_service.py 的 _generate_reverify_prep_jobs(:732)。

規則:產卡時寫 due_at=母輪最後一筆稽核次(外稽)的 POA&M 里程碑日期。其他覆核輪行為不動。

陷阱:母輪的 POA&M 要從最後一筆稽核次取,不能讀輪次上的 poam_id(改綁後語意是「目前」那一次,剛好也是外稽,但讀取來源要統一走稽核次)。

畫面:稽核次切換與進度條(卡 10、12)

規則(P6;⚠ 進度條部分已推翻,見變更紀錄 2026-10-16):

  • 總覽頁輪次下拉底下多一層「第 N 次內部/外部」切換,各自顯示該稽核次的 AP/AR/POA&M;AP/AR/POA&M 標題與 OSCAL props 帶「第 N 次內部/外部」。
  • 進度條照範本站排列,直接讀引擎目前所在的站高亮,不由輪次 status 推算;退回後高亮回到規劃站。
  • 舊輪次 audit_nature 為 NULL,切換顯示「未標示」。

陷阱:進度條若改由輪次 status 推算,內稽與外稽兩段共用同一組狀態值、回流時 status 又回到 planning,推算結果會與引擎實際所在的站不一致——一律讀引擎目前站。


§8

拆分

依「決策者何時看得到畫面」排序。e2e 屬母卡收口,不在單卡做。

卡 名稱 範圍 套件 依賴 驗收條件(決策者親手做什麼、看到什麼)
1 建稽核次表並把既有輪次搬成第 1 次 migration、輪次加 current_pass_id、pass entity/repo jedi-compliance-audit — DEV 任一既有輪次查得到 pass_no=1 一筆,四個值與輪次一致
2 範本站加「稽核性質」下拉並出新公版範本 audit_nature 選單 seed、新公版 BPMN seed、FE 範本編輯器下拉 — — 範本編輯器點稽核計畫填寫站看到「稽核性質」可選內部/外部;公版清單多「內外稽核流程」
3 走到稽核計畫站時開新稽核次 launch_audit 改綁 pass、AP 複製或新建分流、SSP 重拍 jedi-compliance-audit 1、2 用新公版範本走到內部稽核計畫填寫,DB 多一筆內部稽核次、帶 SSP 快照與 AP
4 其餘三站改綁稽核次並支援回到規劃 start_auditing/finalize_audit/close_round 改綁、terminal、回 planning jedi-compliance-audit 3 第 1 次內稽有缺失、改善完成後輪次回 planning;第 2 次內稽產生新的 AR,第 1 次的 AR 還在
5 流程第二次走到同一站時開新任務卡 兩份推進邏輯、attempt_no、依 element id 找 job jedi-flow-engine 4 退回重做後,任務歷程看到同站兩筆,第一次的完成時間還在
6 任務卡加期限欄(後端) due_at ORM/entity/mapper/repo/DTO/serializer/view/API jedi-flow-engine、jedi-task-platform — 用 API 寫入期限,詳情 API 與我的任務查詢都回得到
7 我的任務與任務抽屜顯示期限 FE 兩頁,新邏輯開 composable/子元件 — 6 我的任務列表與抽屜都看到期限;PM 改期限後標「手動調整」
8 內稽不合格自動退回原卡 篩 not_met → 找卡 → 單卡退回函式(自 revert_job 抽出) → 寫期限 → 寫重做紀錄 — 4、5、6 內稽標 3 條不合格並填里程碑,改善完成後原指派人看到 3 張退回卡、期限=里程碑、收到通知
9 任務卡到期提醒 job_due 兩時點(前 3 天、當天)、CHECK 放寬、排程 — 8 把一張卡期限設在 3 天後,排程跑完指派人與 PM 各收到一封提醒信、task_alert_notifications 多一筆 stage due_3d;把期限改成今天再跑一次,再收一封、多一筆 due_today;同一 stage 重跑排程不重寄
10 總覽頁切換第幾次稽核並讓文件標題帶次數 稽核次切換、AP/AR/POA&M 標題帶「第 N 次內部/外部」、OSCAL props — 4 總覽頁輪次下拉底下切換「第 1 次內部/第 2 次內部/第 1 次外部」,各自看到自己的 AP/AR/POA&M
11 覆核輪新卡帶期限 _generate_reverify_prep_jobs 寫 due_at jedi-compliance-audit 6 外稽有缺失並填里程碑、啟動覆核輪,新卡期限=外稽里程碑
12 流程進度條 純 FE,讀引擎目前站(P6) — 5 總覽頁看到照範本站排列的進度條,目前站高亮;退回後高亮回到規劃站

順序:1 → 3 → 4 是主線;2、6 → 7 平行;5、8 接在 4 後;9、10、11、12 收尾。

套件:jedi-compliance-audit(卡 1、3、4、11)、jedi-flow-engine(卡 5、6)、jedi-task-platform(卡 6)。開發期走 poetry path dependency,不發版;發版等決策者明示。

含 migration 的卡(1、5、6、8、9 與卡 2 的 system_menus seed)照 SQL migration 鐵則:日期註解、新表 GRANT ... TO cm_app 與 sequence 權限、收尾 INSERT public.schema_migrations、只套 DEV、寫完回報出貨基線待重產。


§9

端到端驗收

在 DEV 用新公版「內外稽核流程」範本走一輪完整劇本,批次走完再一次回報。每步寫「在哪個畫面/按什麼/看到什麼/DB 查什麼」。

  1. 用新公版範本建專案:流程範本清單 → 打開「內外稽核流程」,看到兩段稽核與一條從「內部缺失改善」往回接「規劃」的虛線;點內部、外部兩個稽核計畫填寫站,「稽核性質」分別是內部、外部。用此範本建專案並啟動 → 專案管理頁看到全部控制項的規劃+執行任務卡。DB:compliance.audit_round_passes 此輪尚無資料。
  2. 完成規劃、走到內部稽核計畫填寫站:指派人在「我的任務」完成全部任務卡 → 流程推進到內部稽核計畫填寫。DB:此輪多一筆稽核次 pass_no=1、audit_nature=internal、nature_seq=1、prev_pass_id 為 NULL、帶 ssp_id 與 assessment_plan_id;project_audit_rounds.current_pass_id 指向它、輪次 status=audit_planning。
  3. 第 1 次內稽 3 條不合格:稽核員填完 AP 送出 → 執行稽核,標 3 條控制項不合格並在改善計畫各填一個里程碑日期 → 完成稽核。畫面:總覽頁 AR 標題帶「第 1 次內部」。DB:稽核次 #1 有 ar_result_id、poam_id,status=remediation;輪次 status 同為 remediation。
  4. 改善完成、退 3 張卡:PM 把 POA&M 全部關閉、完成內部缺失改善站 → 原指派人的「我的任務」出現 3 張退回卡,期限=各自控制項最早的里程碑日期,並收到待辦通知;專案管理頁仍是全部控制項,只有這 3 張變回待處理。DB:稽核次 #1 status=closed;輪次 status 回 planning;這 3 筆 job_executions 的 end_time 為 NULL、due_at 有值;compliance.job_execution_redo_log 多 3 筆,pass_id=#1、prev_end_time=退回前的完成時間。
  5. 改期限:PM 在任務抽屜把其中一張卡的期限往後改 → 抽屜與我的任務都顯示新期限並標「手動調整」;該控制項 POA&M 的里程碑日期不變。
  6. 重做、第 2 次內稽無缺失:指派人重做 3 張卡 → 任務歷程看到同站兩筆,第一次的完成時間還在 → 流程推進到內部稽核計畫填寫。DB:多一筆稽核次 pass_no=2、audit_nature=internal、nature_seq=2、prev_pass_id=#1;這次 AP 只列那 3 條控制項;稽核次 #1 的 AP/AR/POA&M 不變。稽核員稽核後無缺失 → 稽核次 #2 status=closed,輪次沒有結案、流程推進到外部稽核計畫填寫。
  7. 外稽全範圍 2 條不合格:DB:多一筆稽核次 pass_no=3、audit_nature=external、nature_seq=1、prev_pass_id 為 NULL,AP 涵蓋全部控制項。稽核員標 2 條不合格並填里程碑 → 外部 POA&M 建立。總覽頁輪次下拉底下切換「第 1 次內部/第 2 次內部/第 1 次外部」,各自看到自己的 AP/AR/POA&M;進度條高亮在外部缺失改善站。
  8. 外稽 POA&M 完成:PM 關閉外部 POA&M、完成外部缺失改善站 → 輪次 status=pending_reverify;稽核次 #3 status=closed。
  9. 發起覆核輪:PM 啟動覆核輪 → 新的一輪只有那 2 條缺失項的新卡,期限=外稽 POA&M 的里程碑日期。DB:新輪的 job_executions.due_at 有值;母輪三筆稽核次都還在。
  10. 到期提醒:把一張退回卡期限設在 3 天後跑排程 → 指派人與 PM 收到提醒信,task_alert_notifications 多一筆 object_type job_due、stage due_3d;改成今天再跑 → 多一筆 due_today。
  11. 舊資料照常:DEV 任一既有輪次查得到 pass_no=1 一筆,四個值與輪次一致、audit_nature 為 NULL、總覽頁顯示「未標示」;用既有公版範本跑一輪,行為與上線前相同(稽核無缺失直接結案)。

§10

不做與後續

  • 角色重構、小組長兩級審核、外稽員只看自己那次(角色題另案處理)。
  • 專案層級範本(一張圖自動開輪)。
  • 舊資料回填 audit_nature(舊輪次顯示「未標示」)。
  • 同範本內把覆核輪也改成輪內回流:外稽不過維持開新輪(決策者明示)。
  • 雛形不另刻;期限兩頁與總覽切換的畫面由卡 7、10、12 實作時依本文件規則刻。

§11

坑與陷阱總表

開工前先掃這張表,每列都對應一張卡。

# 坑 後果 對應卡
1 推進邏輯有兩份(套件與主專案) 只改一份,兩條路行為分歧 5
2 _find_job_by_stage_code 只回第一筆 同站碼出現兩次時找錯站 5
3 閘道條件字串是全等比對 多一個空白閘道就判斷失敗 2
4 內部缺失改善 → 規劃沒標 reverse allowed_successors 檢查擋下範本 2
5 start_auditing 的 not ar_result_id 第二次稽核沿用第一次的 AR 4
6 launch_audit 直接覆寫 ssp_id/assessment_plan_id 第二次稽核蓋掉第一次 3
7 四槽位讀取者分散在主專案與套件十餘處 漏改一處就靜默讀到舊的一次 3、4
8 MyTasksView.vue、JobExecutionDrawer.vue 超標 往原檔加邏輯違反體積上限 7
9 view 重建沒帶 security_invoker 繞過 RLS 6
10 task_alert_notifications 的 object_type CHECK 新增 job_due 寫入被擋 9
11 wecm control_id 格式與 AR finding target_id 的對應未驗證 退卡找不到卡或找錯卡,實作前先用 DEV 資料驗 8
12 job repo update 只寫 status 與時間 due_at 寫了不存 6