---
title: "一輪內走完內稽與外稽 — 討論稿 (FR-140)"
brand: "Guidant AI · **FR-140** 一輪內走完內稽與外稽"
eyebrow: "FR-140 · 討論稿 · 2026-10-15"
h1: "一輪一條流程，內稽回流、外稽覆核"
subtitle: "D1～D6、P1～P6 全部已裁；六塊設計、十二張卡"
lede: "一個稽核輪次要能走完整條流程：專案規劃與執行 → 內部稽核 → 有缺失就改善並退回原任務卡重做、再稽核一次 → 內部沒缺失才進外部稽核 → 外部有缺失走既有的覆核輪。每一次稽核（第 1 次內稽、第 2 次內稽、外稽）各自留一組 SSP 快照、評估計畫、評估結果、改善計畫，不互相覆蓋。這份把決策者 2026-10-15 確認的流程、已裁的 D1～D6 與 P1～P6 展開成可以開工的規格；每項現況都附程式碼座標。"
chips: [
  {text: "已裁 D1～D6", kind: ok},
  {text: "P1～P6 已裁", kind: ok},
  {text: "十二張卡，範本編輯器先出畫面", kind: accent},
  {text: "動三支套件", kind: crit}
]
footer: "FR-140 · 討論稿 · 2026-10-15 · 輸入：決策者 2026-10-15 流程圖與裁示、PM 一輪範圍裁決 · 現況依據：程式碼行號（2026-10-15）"
---

> 全部已裁，下一步寫 design.md，本稿不再改寫。

**狀態**：討論稿（D1～D6、P1～P6 全部已裁，下一步寫 design.md、開 Notion 卡）　｜　**日期**：2026-10-15　｜　**母卡**：CM-2765

路徑慣例：BE 路徑相對 `compliance-manager-be/`；`pkg/` 代表 `~/Projects/Jedicogy/module/jedi-python-package/`；FE 路徑相對 `compliance-manager-fe/`。

名詞：**輪次**＝稽核輪次（`compliance.project_audit_rounds`），一輪對應一次專案稽核流程；**稽核次**＝本案新增的概念，一輪內每做一次稽核（內部或外部）就是一筆；**AP**＝評估計畫（assessment plan）；**AR**＝評估結果（assessment results）；**POA&M**＝缺失改善計畫（plan of action and milestones）；**job**＝流程引擎依範本站產生的任務卡執行紀錄（`compliance.job_executions`）；**站**＝範本（BPMN 流程圖）上的一個使用者任務節點；**reverse 連線**＝範本上標記為「往回走」的連線。

## 變更紀錄 {#changelog nav="變更紀錄"}

| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-15 | 初稿：需求流程圖、現況盤點、稽核次概念與資料模型草案、端到端流程、已裁 D1～D6、待裁 P1～P6、六塊詳細設計、十二張卡拆卡草案 | — |
| 2026-10-15 | 決策者裁 P1～P6：P4 改為到期前 3 天與到期當天兩個時點，其餘照建議 | P1～P6 |

---

## 分工概述（30 秒版） {#overview nav="概述"}

整案一句話：**一輪就是一條完整流程，內稽不合格就把那幾張原任務卡退回重做、再稽核一次，內稽過了才進外稽；每一次稽核各留一組 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）。

```{.mermaid cap="圖 1 — 十二張卡的依賴（箭頭＝必須先做）"}
%%{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
```

---

## 需求 {#need nav="需求"}

```{.mermaid cap="圖 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 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["覆核輪（現有機制，新的一輪）"]
```

逐句需求（決策者 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 縮到不合格項 | 只建缺失項 |

---

## 現況盤點：接入點 × 現況 × 本案動作 {#inventory nav="盤點"}

### ① 流程引擎（兩份推進邏輯，都要改）

| 接入點 | 現況 | 本案動作 |
|---|---|---|
| 建 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 沒有通知 | 退卡 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／子元件新檔 |

::: {.callout .crit}
**🔴 真正的牆在輪次層，不在引擎**

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

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

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

---

## 核心概念：稽核次 {#pass nav="稽核次"}

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

::: grid2
::: {.card .ok}
#### Living SSP 怎麼運作

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

::: {.card .ok}
#### 稽核次怎麼運作

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

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

---

## 資料模型草案 {#model nav="資料模型"}

### 新表：稽核次 `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） |
| `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` | 指向目前稽核次；輪次上四個槽位保留，跟著目前稽核次同步寫（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` | 退回時間 |

### migration

既有輪次各建一筆 `pass_no=1`：`audit_nature` 取範本站屬性，沒有就 NULL；四槽位複製過去；`current_pass_id` 指向它。舊範本一輪一次照常跑（D6）。

```{.mermaid cap="圖 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'}}}%%
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 : "因這次退回"
```

---

## 端到端流程 {#flow nav="流程"}

以「第 1 次內稽有 3 條不合格 → 退卡重做 → 第 2 次內稽無缺失 → 外稽有缺失 → 開覆核輪」為例：

```{.mermaid cap="圖 4 — 一輪走完內稽回流與外稽"}
%%{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＝外稽里程碑
```

每站 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 | 篩 not_met 控制項 → 找原卡 → 退回、寫期限、寫重做紀錄、通知 |
| 外部缺失改善完成（下一個是結束事件） | `close_round`（維持現有） | 這次 closed，母輪 → pending_reverify | 無（覆核輪產卡時再寫期限） |
| 啟動覆核輪 | `launch_reverify`（不動） | 新的一輪 | 新卡承襲，`due_at`＝外稽 POA&M 里程碑 |

---

## 已裁決策 {#decisions nav="已裁"}

::: {.callout .decided}
**✅ D1 — 內稽不合格原卡退回，外稽不合格走覆核輪**

**定案**：內稽不合格的任務卡原卡退回：status 回待處理、end_time 清空，指派人、部門、設備、證據都不動；另寫一筆重做紀錄，保留歷次完成時間。外稽不合格走覆核輪，開新卡。

**理由**：內稽回流是同一輪內的修正，PM 要在同一張全控制項的圖上看到哪幾張卡退回；原卡退回讓指派人在原地補做，證據與指派關係不必重建。外稽是對外的正式結果，維持既有覆核輪機制。
:::

::: {.callout .decided}
**✅ D2 — 第 N 次內稽 AP 縮到不合格項，外稽 AP 全範圍**

**定案**：第 N 次（N≥2）內稽的 AP 從上一次內稽的 AP 複製，縮到 not_met 控制項，沿用覆核輪的 clone 邏輯，只是來源改成同一輪上一次稽核次。外稽 AP 一律全範圍新建。

**理由**：第 2 次內稽只是確認上次不合格的項目改好了，不必重查全部；外稽是正式稽核，必須涵蓋全範圍。clone 邏輯已在覆核輪驗證過，換來源即可重用。
:::

::: {.callout .decided}
**✅ D3 — 稽核次表＝把輪次四個單值槽位改成一輪多筆**

**定案**：新增稽核次表，一輪多筆，每筆帶 SSP 快照、AP、AR、POA&M。概念同 living SSP：活本是任務卡與證據，每次走到「稽核計畫填寫」站凍一組。

**理由**：四個槽位是單值，第二次稽核必然覆寫第一次；需求要求每一次都留得下來且分得出內外與次數，只能改成多筆。
:::

::: {.callout .decided}
**✅ D4 — 不改引擎的走法與範本驗證規則**

**定案**：引擎 token 怎麼走、範本驗證規則都不動；只改「回到同一站時新開一筆 job」與站 handler 的綁定。

**理由**：盤點顯示範本已畫得出退回再稽核的圖（同站碼可重複、reverse 與閘道都跳過檢查），問題只在 job 重用與輪次單向，改動範圍收在這兩處最小。
:::

::: {.callout .decided}
**✅ D5 — 覆核輪機制保留不動，只補期限承襲**

**定案**：覆核輪照現有方式開新輪、新卡承襲、只建缺失項；唯一新增是新卡帶上期限。

**理由**：外稽不過維持開新輪是決策者明示；期限是本案新加的欄位，覆核輪的卡也要有才一致。
:::

::: {.callout .decided}
**✅ D6 — 舊資料與舊範本照常跑**

**定案**：migration 把既有輪次的四槽位搬成第 1 次稽核次；舊範本一輪一次照常跑。

**理由**：既有輪次與範本不能因本案失效；搬成第 1 次後，所有讀取者統一讀「目前稽核次」即可，不必分新舊兩條路。
:::

---

## 補裁 P1～P6 {#pending nav="補裁"}

決策者 2026-10-15 已全部裁定：P4 改為到期前 3 天與到期當天，其餘照建議。

::: {.callout .decided}
**✅ P1 — 稽核次要不要記「是哪一次的重做」（`prev_pass_id`）**

問題：第 2 次內稽是第 1 次的重做，要不要在稽核次上直接記這個關係。

選項：A. 記 `prev_pass_id`；B. 不記，靠 `audit_nature`＋`nature_seq` 推。

[**定案（照建議）**：A，要記。畫面才串得出「第 1 次 → 第 2 次」的軌跡，不必用序號推算。]{.rec}
:::

::: {.callout .decided}
**✅ P2 — 輪次自己的 status 保留七態還是收成粗狀態**

問題：狀態機改綁稽核次後，輪次的 status 要怎麼處理。

選項：A. 保留欄位與七態，跟著目前稽核次同步寫；B. 收成粗狀態（進行中／待覆核／結案）。

[**定案（照建議）**：A。讀輪次 status 的地方很多，保留欄位與七態不動，斷言改綁稽核次，輪次 status 跟著目前稽核次同步寫，舊畫面不會壞。]{.rec}
:::

::: {.callout .decided}
**✅ P3 — 第 2 次內稽無缺失時輪次怎麼不被結案**

問題：`finalize_audit` 遇到沒有不合格就把輪次設 closed（`audit_round_app_service.py:513`），但第 2 次內稽之後還有外稽。

選項：A. `finalize_audit` 加參數 `terminal`，由 handler 依「這站之後是否接結束事件」決定；B. 一律不設 closed，改由範本結束事件結案。

[**定案（照建議）**：A。`terminal` 由 handler 看範本決定，舊範本（稽核站後直接接結束）行為不變。]{.rec}
:::

::: {.callout .decided}
**✅ P4 — 到期提醒的時點與對象**

問題：任務卡有了期限，什麼時候提醒、提醒誰。

選項：A. 前 7 天、前 1 天、逾期當天，寄指派人＋PM；B. 只在逾期當天寄指派人。

[**定案**：到期前 3 天、到期當天兩個時點，寄指派人＋PM；沿用 `task_alert_notifications`，object_type 新增 `job_due`，stage 為 `due_3d`／`due_today`。]{.rec}
:::

::: {.callout .decided}
**✅ P5 — 期限可不可以由 PM 手改**

問題：期限是從 POA&M 里程碑帶過來的，PM 要不要能改。

選項：A. 可改，改了標「手動調整」，不回寫 POA&M；B. 不可改，只能去改 POA&M。

[**定案（照建議）**：A。改了標「手動調整」，不回寫 POA&M，兩邊的責任分清楚。]{.rec}
:::

::: {.callout .decided}
**✅ P6 — 進度怎麼顯示**

問題：一輪內有多次稽核，總覽頁要怎麼讓人看出現在走到哪。

選項：A. 輪次下拉底下多一層「第 N 次內部／外部」切換，另畫一條照範本站排列的進度條；B. 只加切換，不畫進度條。

[**定案（照建議）**：A，進度條直接讀引擎目前所在的站，不推算。]{.rec}
:::

---

## 詳細設計 {#design nav="設計"}

### 一、稽核次表

**改哪些檔**：新 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 次稽核次必須補齊，否則所有改讀目前稽核次的地方都會讀到空值。

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

**改哪些檔**：`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 跟著目前稽核次同步寫（P2）。
- 走到稽核計畫填寫站時，handler 先「開新稽核次」：讀站屬性 `audit_nature`；內稽第 N 次（N≥2）複製上一次內稽 AP 縮到 not_met；外稽全新 AP；每次重拍 SSP 快照。
- `start_auditing` 每次新建 AR result，在同一個 engagement 的 AR 根底下追加，標題帶「第 N 次內部／外部」。
- `finalize_audit` 加 `terminal` 參數（P3）。
- `close_round`：下一條是 reverse 回規劃時，這次設 closed、輪次 status 回 planning、觸發退卡；下一個是結束事件時維持現有（母輪 → pending_reverify 供覆核）。

**陷阱**：

- `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`（略過輪次凍結檢查、保留通知）→ `due_at` 寫該控制項 POA&M 最早的 `target_date` → 寫一筆重做紀錄（job_execution_id、pass_id、prev_end_time、revert_at）。
- 提醒 `job_due` 兩個時點：到期前 3 天、到期當天（P4 定案）。
- PM 可手改期限，標「手動調整」（P5）。

**陷阱**：

- 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`（改綁後語意是「目前」那一次，剛好也是外稽，但讀取來源要統一走稽核次）。

---

## 拆卡草案（依「決策者何時看得到畫面」排序） {#cards nav="拆卡"}

| 卡 | 名稱 | 範圍 | 套件 | 依賴 | 驗收 |
|---|---|---|---|---|---|
| 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 天後，排程跑完收到提醒信；到期當天再收一封 |
| 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，不發版。

---

## 不做清單 {#out nav="不做"}

- 角色重構、小組長兩級審核、外稽員只看自己那次（角色題另案處理）。
- 專案層級範本（一張圖自動開輪）。
- 舊資料回填 `audit_nature`（舊輪次顯示「未標示」）。
- 同範本內把覆核輪也改成輪內回流：外稽不過維持開新輪（決策者明示）。
- 本稿不附雛形；期限兩頁與總覽切換在 design 階段再刻。

---

## 坑與陷阱總表 {#pitfalls nav="坑"}

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

| # | 坑 | 後果 | 對應卡 |
|---|---|---|---|
| 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 |
