---
title: "改善計畫（POA&M）逐評估目標重做 — 設計定案 (FR-143)"
brand: "Guidant AI · **FR-143** 改善計畫重做"
eyebrow: "FR-143 · 設計定案 · 2026-10-17"
h1: "改善計畫重做：設計定案"
subtitle: "左樹只列不符合、稽核缺失文件表單、對原任務逐一設執行人員／改善期限／改善措施、送出即退回第一站重跑、側邊入口表格、Excel 往右延伸一列一任務、OSCAL POA&M 匯出合規"
lede: "缺失改善頁整個換掉。左樹只列這次判不符合的評估目標；點一條，上半是「稽核缺失」文件表單（稽核發現、風險、稽核建議、稽核人員、查核方法、採用佐證），下半列出這條評估目標底下的原任務，PM 對每個任務設執行人員（可多人）、改善期限、改善措施，不新增任務。只排計畫不追蹤完成，送出就推進：內稽每個任務退回第一站整條重跑、改善期限寫進每一站；外稽進待覆核。側邊選單多一個「改善計畫」表格入口。Excel 與稽核計畫、稽核結果同一份往右延伸，一列一個任務、合併儲存格。資料照 OSCAL POA&M 標準落點，並修 jedi-oscal-v2 四處匯出不合規。本文件寫定案規則、為什麼這樣定、踩到會怎樣。"
chips: [
  {text: "D1～D12 全部定案（2026-10-17）", kind: ok},
  {text: "六棒六張卡，棒 1 套件修正先做", kind: accent},
  {text: "動兩支套件：jedi-oscal-v2、jedi-compliance-audit", kind: crit},
  {text: "陷阱：現行退卡只重開最新一站，多站流程退錯人", kind: crit},
  {text: "不做報告 Word／PDF、不做舊資料", kind: warn}
]
footer: "FR-143 · 設計文件 · 2026-10-17 · 討論稿：[discussion.md](discussion.md)　雛形：[prototype/index.html](prototype/index.html) · 沿革見討論稿與 git log"
---

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

> 狀態：**設計定案（2026-10-17）**｜母卡 [CM-2840](https://app.notion.com/p/FR-143-POA-M-Excel-OSCAL-3fc346da4cd081acb7a5df86530a073d)｜子卡 CM-2841～2846｜討論稿：[`discussion.md`](discussion.md)｜雛形：[`prototype/index.html`](prototype/index.html)｜範例 Excel：[`prototype/sample/FR-143-改善計畫範例.xlsx`](prototype/sample/FR-143-改善計畫範例.xlsx)

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

名詞：**評估目標**＝AO，控制項底下的檢查項目，全系統用 part_id 當鍵（`<控制項代碼>_obj.N`）；**POA&M**＝改善計畫（plan of action and milestones），OSCAL 評估層第三個文件；**poam-item**＝一條缺失的封面，引用 finding；**risk**＝風險；**response**＝風險的處置計畫（表 `assessment_remediations`，程式稱 remediation），`lifecycle`：recommendation 稽核建議／planned 改善計畫；**task**＝response 底下一筆（表 `poam_milestones`），本案一筆 task 對一個系統任務；**任務**＝一條工作流程（`compliance.workflow_executions`），底下可能多站（`job_executions`，例如證據上傳 → 主管檢核 → 顧問檢核）；**稽核次**＝FR-140 的 `audit_round_passes`，一輪內每做一次稽核一筆，各自一組 AP／AR／POA&M。

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

| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-17 | v1：D1～D12 定案；資料模型與 OSCAL 落點、API、畫面、Excel 契約、退卡規則、六棒六張卡、端到端驗收 | 母卡 CM-2840 |
| 2026-10-17 | v1.1：修任務粒度自相矛盾——一塊對一條流程（不是一站一塊），執行人員只改第一站；D3、D5、D10、驗收 2 與 5 改寫 | 棒 2、5 驗收時發現，決策者裁 A 案 |
| 2026-10-17 | v1.2：D9 清單改依專案分組展開（與 FR-142 稽核作業同一套），小字「共 N 份改善計畫」；待規劃判準補輪次仍在 remediation | 決策者裁；棒 7 CM-2851 |

---

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

整案一句話：**缺失改善頁換成左樹只列不符合評估目標、中間上半看稽核缺失、下半對原任務逐一設執行人員／改善期限／改善措施，送出就把任務退回第一站重跑不等改完；側邊選單多一個「改善計畫」表格入口；Excel 與前兩階段同一份往右延伸；POA&M 匯出合規。**

| 卡 | 做什麼 | 產出 | 完成怎麼判定（決策者親手做什麼、看到什麼） |
|---|---|---|---|
| **1** | 套件 POA&M 匯出合規與守門改法 | jedi-oscal-v2 匯出修四處；jedi-compliance-audit 守門改「計畫齊全」、task 對應任務 | DEV 一個有不符合的稽核次送出改善計畫後，匯出 OSCAL JSON 用官方 schema 驗證通過，檔內每個 task 有改善期限與執行人員 |
| **2** | BE 改善計畫端點 | 新 app service、route、schema、error code | Swagger 存一條有兩條流程的評估目標讀回一致；有流程沒填齊送出回 412 列出是哪個；送出後再存回 409 |
| **3** | FE 改善計畫頁 | 新頁、子元件、舊頁退役 | 對照雛形畫面 1 逐頁操作，每個動作對應後端真資料 |
| **4** | Excel 匯入匯出 | workbook 讀寫、預覽 | 匯出改兩列匯回，預覽標出、確認後畫面更新；寫一個不是成員的名字預覽要求處理 |
| **5** | 退卡改第一站重跑、改善期限寫每一站 | 新 `job_restart_service.py`、退卡與覆核輪改接 | 三站流程全完成後送出改善計畫：第一站回進行中、後兩站回待辦、三站改善期限都是計畫日期、第一站指派人＝執行人員、後兩站指派人不變 |
| **6** | 側邊選單「改善計畫」入口 | 讀模型、端點、FE 表格頁、路由 seed | PM 帳號登入，側邊選單「改善計畫」看到待規劃稽核次表格、點列進該輪；只當 auditor 的帳號看不到或為空 |
| **7** | 清單改依專案分組展開 | FE 頁改寫、共用分組 composable | 「全部」頁籤同專案只一列、點列展開子列由舊到新、「目前」標在最急那次、全部展開／收合、搜稽核次自動展開；稽核作業頁行為不變 |

依賴：1 先做 →（2、4、5 可平行）→ 3 等 2；6 等 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
    C1[1 套件修正] --> C2[2 BE 端點]
    C1 --> C4[4 Excel]
    C1 --> C5[5 退卡重跑]
    C2 --> C3[3 FE 頁面]
    C2 --> C6[6 入口清單]
```

---

## 決策定案 {#decisions nav="決策"}

全部由決策者 2026-10-17 拍板，討論過程見討論稿。

**D1 粒度＝逐不符合評估目標；控制項只是分組**
左樹到評估目標，每條各自規劃；控制項總覽只是彙總。OSCAL poam-item 對 finding（FR-142 起 finding 已是評估目標層）。排除：控制項層一筆（第 2 次內稽縮範圍是評估目標層，對不上）。

**D2 只排計畫不追蹤完成**
沒有打勾、沒有進度條；改好沒有＝下一次稽核判定。`poam_milestones.status`／`completed_at` 停用不刪。排除：保留打勾但不當守門（兩套真相）。

**D3 不新增任務；對評估目標底下的原任務逐一設執行人員、改善期限、改善措施；守門＝每個任務三欄齊；送出後鎖定**
「任務」＝一條工作流程（`workflow_execution_control_mapping` 掛的那一層），一條流程底下可能多站（證據上傳 → 主管檢核 → 顧問檢核）；**畫面一塊對一條流程，不是一站一塊**。站只唯讀顯示名稱與狀態。一條評估目標底下有幾條流程就列幾塊，有缺失就都要退回，沒有「納不納入」。每塊：執行人員（可多人，預設第一站原指派人）、改善期限（即里程碑）、改善措施（可一鍵帶入稽核建議）。有任務沒填齊就擋送出並列出「任務名 未填：X」；送出後整份唯讀。排除：一站一塊（檢核站的人是流程範本定的，不該在改善計畫頁換；PM 要填三倍）；PM 自建改善任務清單；一個任務多個里程碑；任務可勾不納入；送出後仍可改改善期限。

**D4 改善期限逐任務設定、寫回該任務；一個任務對多條評估目標時共用執行人員與改善期限**
畫面上同一任務在第二條評估目標出現時顯示「此任務同時對應 X，執行人員與改善期限一併套用」。排除：一條評估目標一個改善期限套全部任務；取最早或最晚。

**D5 執行人員＝流程第一站的指派人，可多人，預設帶第一站原指派人，PM 可加減；送出時只改寫第一站的 `task_assignees`，後續站維持原人**
DEV 實查三站流程每站各有其人（執行者／主管／顧問）；改善計畫的執行人員是「誰去改」，檢核站的人不因排計畫而換。送出確認表註明相較原指派人增減了誰。排除：三站都換成執行人員（執行人員自己檢核自己）；計畫上另記負責人、任務指派人不動。

**D6 Excel＝與稽核計畫、稽核結果同一份往右延伸；一列一個任務；同評估目標的灰底欄合併儲存格**
契約見「Excel 契約」。決策者原則「每階段往右加欄，最後是完整軌跡」（FR-142 D9 已定）；一格換行 user 一定填錯、往右長一組 user 不會用。排除：一格多值換行；每任務往右長一組；砍計畫欄只留重點；執行人員拆三欄。

**D7 匯出兩個：Excel 工作表、OSCAL POA&M JSON；不做 Word／PDF 報告**
OSCAL POA&M 頂層沒有文件正文欄位（AP 有 `terms-and-conditions`，POA&M 只有 metadata／remarks），硬放等於自創欄位；業界 POA&M 交付物就是表格（FedRAMP 官方範本是 Excel）。排除：報告 docx／pdf；概要頁籤；併進稽核報告。

**D8 舊資料不管**
既有輪次的改善計畫不遷移、不處理。新頁只服務本案上線後結案的稽核次。排除：寫遷移；新頁唯讀顯示舊資料。

**D9 側邊選單加「改善計畫」入口（不加「我的」），表格依專案分組展開，列出我是專案經理的輪次**
與 FR-142 稽核作業同一套：**一專案一列**，專案列的值取「目前這一次」（待規劃 ＞ 已送出 ＞ 覆核中 ＞ 已結束，同狀態取最晚期限最近）；專案名下小字「共 N 份改善計畫 · 第 R 輪 · 標準」（N＝該專案有改善計畫的稽核次數，決策者裁 A 案）。點專案列展開子列：該專案每一份改善計畫依結案日由舊到新，「目前」加粗標 tag；只有一份的不展開、小字直接寫稽核次、點列直接進。預設展開有待規劃的專案；工具列「全部展開／收合」。欄位：狀態、已規劃／不符合、退回任務數、最晚期限（30 天內橘、過期紅）、稽核結案、送出日。狀態頁籤仍算稽核次、預設「待規劃」，篩選後子列只剩符合的並加「（符合篩選 M 次）」；可篩稽核性質與標準、搜專案名與稽核次（命中子列自動展開並標黃）。排序、分頁以專案為單位，每頁 10 個專案。前端一次撈全部稽核次後分組；分組 composable 與稽核作業頁共用（`useAuditWorkGroups`，注入預設展開規則與專案列額外欄位）。登入者判準只看「是該專案 manager」；待規劃＝輪次仍在 remediation 且為目前稽核次且改善計畫未送出。理由：排計畫的人是 PM，入口藏在專案三層內是「不知道怎麼用」的原因之一；同專案多次稽核平列時看不出卡在哪。排除：卡片式；平列一稽核次一列（第一版，決策者改判分組）；進度條；小字寫「共 N 次稽核」（B 案，子列只列有改善計畫的那幾次，數字對不上）；併進「我的稽核」當分頁。

**D10 退卡＝每個任務退回第一站整條重跑**
現行內稽退卡只把最新那一站改回進行中（`not_met_job_redo_service._redo_one` 用 `pick_latest`），三站流程全完成時重開的是顧問檢核，補證據的人改不到；DEV 現有 6 筆退卡紀錄全是單站流程，沒被測出來。定案：第一站改回進行中、通知第一站指派人（＝執行人員）；後續站改回待辦、保留原指派人，前一站完成才依序往下。改善措施照舊寫成任務上的系統留言。外稽覆核輪產新任務同理從第一站開始。排除：只重開最新站；讓 PM 選退到哪一站。

**D11 不設「暫行緩解措施」欄**
改好之前先做的臨時防護本來就該寫進該任務的改善措施。OSCAL `risk.mitigating-factors` 匯出留空。

**D12 改善期限寫進該任務的每一站，不加欄位、不動 jedi-flow-engine**
期限欄 `job_executions.due_at` 已存在（FR-140），但只在每一站上，流程主表沒有；`complete_job` 推出下一站不帶期限。定案：退卡時把 PM 設的改善期限寫進該任務全部站的 `due_at`（`due_at_manual=false`）。排除：流程主表加欄（動套件）；流程變數；只壓第一站。

---

## 現況接入點 {#inventory nav="接入點"}

### ① POA&M 資料模型（套件 jedi-oscal-v2，schema `oscal`）

三層既有，不加表：`assessment_risks`（status、deadline、mitigating_factors）→ `assessment_remediations`（lifecycle、title、description）→ `poam_milestones`（task_type、title、description、assignee_user_id、assignee_org_unit_id、target_date、status、completed_at、props）。`poams`（status）、`poam_items`（related_findings）。產生：`pkg/jedi-oscal-v2/app/service/poam/poam_service.py:54 generate_from_findings`，FR-142 結案 `finalize_assessment` 呼叫，每筆 not_met finding 一個 item（finding 已是評估目標層，粒度自然對）。

### ② 匯出（`pkg/jedi-oscal-v2/app/service/io/oscal_io_service.py`，1,697 行超標）

`_risk_to_json`（:1538）、`_remediation_to_json`（:1564）、`_milestone_to_task_json`（:1584）、`_export_poam`（:1600）、`_poam_item_to_json`（:1685）。四個不合規見「OSCAL 落點」。**超標檔只准新開 `poam_export_parts.py` 再從原檔一行呼叫。**

### ③ 套件服務與主專案端點

- `pkg/jedi-compliance-audit/app/service/poam_app_service.py`（493 行）：`list_items`、`get_item`、`add_remediation`、`add_milestone`、`update_milestone`、`all_items_closed`（:421）、`earliest_milestone_dates`（:434）、`milestones_by_control`（:449）。
- `pkg/jedi-compliance-audit/app/service/audit_round_pass_transitions.py:96 _close_on_pass`：`all_items_closed` 不成立拋 `GRC_POAM_NOT_ALL_CLOSED`（GRC_412005）——這就是「要等改完才能走」的根。
- `pkg/jedi-compliance-audit/app/service/audit_round_app_service.py`（918 行超標）：`finalize_audit`（:470）、`close_round`（:522）、`launch_reverify`（:543）。
- 主專案 `api/project/__init__.py:109-114` 掛 `/audit-round/<round_uid>/poam-items`、`poam-item/<item_uid>`、`remediation/<uid>`、`poam-milestone/<uid>`；route 在 `api/project/routes/audit_round_route.py:380-485`，守門 `@require_capability("grc.audit.read")`（讀）、`grc.audit.create`／`grc.audit.update`（寫）。
- 退卡：`app/flow_control/service/not_met_job_redo_service.py`（126 行）`redo_not_met_jobs` → `_redo_one` → `app/flow_engine/service/job_reopen.py reopen_job_execution`；期限 `job_due_date_service.due_at_from_milestone_date`（台北 23:59:59）。
- 流程退回：`app/flow_engine/service/workflow_execution_service.py:787 revert_job`（目標站重開、後續站改 TODO；帶 `assert_job_operator` 與輪次凍結守門，系統退卡不可直接呼叫）。
- 錯誤碼：`common/code/flow_control_error_code.py:100-109、285-286`（GRC_POAM_*）。

### ④ FE 現況

`src/views/project/RoundPoamView.vue`（890 行超標）掛 `/project/projects/:id/round/:roundUid/poam`（router `index.js:1130`）；端點常數 `api.js:296-298`；i18n `round-audit-poam.json`。本案整頁退役，只改一行導向新頁。「我的稽核」入口：`MyAuditsView.vue`（320 行）、`/project/audit-manage`、BE `app/readmodel/service/auditor_dashboard_service.py`＋`infra/readmodel/audit/auditor_dashboard_query.py`（162 行）；路由 seed 先例 `scripts/sql/2026-04-27-add-audit-and-cloud-integration-permissions.sql:31-47`。

### ⑤ 可沿用

FR-142 的頁面骨架、Excel 四頁（`ap_workbook_*`／`ar_workbook_*`）、匯入預覽模式；任務指派人 `task_assignees`（多人）；退卡系統留言 `not_met_redo_comment.build_redo_comment`；任務到期提醒（FR-140 P4）不動。

---

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

### OSCAL 落點（POA&M，v1.2.2）

| 畫面 | OSCAL | DB |
|---|---|---|
| 一條不符合評估目標 | `poam-items[]`：`title`／`description` 抄 finding、`related-findings[{finding-uuid}]`、`related-risks[{risk-uuid}]` | `oscal.poam_items`（既有產生） |
| 稽核缺失（唯讀） | 從 AR 讀 finding、observation、risk、remediation lifecycle=recommendation；匯出時內嵌進 POA&M 檔 | 不重存 |
| 改善計畫（整組） | `risk.remediations[]` 一筆 `lifecycle=planned`、`title`＝「改善計畫」 | `oscal.assessment_remediations` |
| 一個任務的設定 | `response.tasks[]` 一筆：`type=action`、`title`＝任務名、`description`＝改善措施、`timing.on-date.date`＝改善期限、`responsible-roles[{role-id:'remediation-owner', party-uuids:[…]}]`＝執行人員、`props[name=job-uid]`＝任務流程 uid | `oscal.poam_milestones`：`task_type=action`、`title`、`description`、`target_date`、`props.job_uid`、`props.assignee_user_ids[]`（多人全部進 props，`assignee_user_id` 存第一人向後相容） |
| 送出 | `risk.status=remediating`、`risk.deadline`＝該項最晚改善期限；`poams.status=submitted`；同時寫回任務指派人、各站 `due_at`、系統留言 | `assessment_risks.status`／`deadline`、`oscal.poams.status`、`compliance.task_assignees`、`compliance.job_executions.due_at` |
| 鎖定 | 以 `poams.status` 判 | — |

官方必填：poam-item（title／description）、response（uuid／lifecycle／title／description）、task（uuid／type／title）、risk（uuid／title／description／statement／status）。執行人員對 party-uuid：專案參與者在 SSP `metadata.parties` 有對應 party（既有 `matched_user_id`），查不到的只寫 `props`（name）不寫 responsible-roles，不造假 uuid。`mitigating-factors` 留空（D11）。

**匯出四處修正（棒 1）**：

| # | 現況 | 修法 |
|---|---|---|
| 1 | `_milestone_to_task_json` 沒帶 timing／responsible-roles | 補 `timing.on-date.date`＝target_date、`responsible-roles` 反查 party-uuid |
| 2 | `related-findings` 塞私有鍵 `finding-id`（官方 additionalProperties:false） | 匯出剝掉；DB 繼續存 |
| 3 | POA&M 檔 `findings[]`／`risks[]`／`observations[]` 空，poam-item 引用懸空 | 匯出時把被引用的 finding、risk（含 remediations）、observation 寫進 POA&M 檔（同 uuid，組裝不複製 DB） |
| 4 | task 自創 `status`／`completed_at` | 不匯出；DB 欄不刪 |

### 讀寫 API（棒 2）

掛在既有 `/audit-round/<round_uid>` 下，讀取以 FR-140「目前稽核次」取 POA&M（GET 可帶 `pass`）。新邏輯一律新檔 `app/flow_control/service/poam_objective_app_service.py`，不往套件 493 行的 `poam_app_service.py` 加。

| 方法 | 路徑 | 用途 |
|---|---|---|
| GET | `…/poam/objectives` | 只列不符合的控制項樹（族群→控制項→評估目標）、每條的稽核缺失（finding、risk、recommendation、稽核人員、查核方法、採用佐證）、底下的任務（流程 uid、名稱、原指派人、站數與狀態、目前設定：執行人員／改善期限／改善措施）、計畫狀態、統計六格、鎖定旗標 |
| PUT | `…/poam/objective/<part_id>` | 存一條評估目標底下全部任務的設定（陣列：job_uid、assignee_user_uids[]、due_date、advice）；同一任務在別條評估目標也存在時一併套用；回傳該控制項最新狀態與統計 |
| POST | `…/poam/submit` | 守門（每個任務三欄齊，412 列「任務名 未填：X」）→ `risk.status=remediating`、`deadline`、`poams.status=submitted` → 寫回任務指派人與各站改善期限、系統留言 → 呼叫既有 `close_round`（內稽退卡走棒 5 的第一站重跑；外稽進待覆核）→ 鎖定 |
| GET | `…/poam/export?format=xlsx\|oscal` | 匯出二選一 |
| POST | `…/poam-xlsx/parse`、`/poam-xlsx/<parse_uid>/confirm` | Excel 匯入預覽與確認（棒 4） |

**鎖定**：送出後所有寫入端點回 409（新 error code `GRC_POAM_SUBMITTED_LOCKED`（「改善計畫已送出，內容不可修改」），`GRC_409042`（現有最大 409041））。**權限**：route 掛 `grc.audit.read`（讀）／`grc.audit.update`（寫，同既有 `PoamMilestoneRoute`）；app service 內再以 `common/authz` 資源域守門要求專案 manager 才可寫，auditor 可讀。**守門改法（棒 1）**：`_close_on_pass` 的 `all_items_closed` 改成 `all_items_planned`（每個 item 的每個 task 都有 target_date、assignee、description）；`GRC_POAM_NOT_ALL_CLOSED` 文案改「尚有任務未完成規劃」。

**任務對應**：一條評估目標底下的任務＝`workflow_execution_control_mapping` 對到該控制項（`control_id`）的流程；FR-141／142 已逐評估目標對應者取 `ao_part_id`，否則控制項層全部任務都算（與 FR-142 D8 佐證候選同源）。

### 畫面（棒 3）

照雛形 [`prototype/poam-authoring.html`](prototype/poam-authoring.html)。FE 新檔 `src/views/project/RoundPoamPlanView.vue`＋`src/components/grc/poam/`：`PoamObjectiveTree.vue`（左樹只列不符合、燈號已規劃／部分規劃／未規劃）、`PoamControlOverview.vue`、`PoamObjectiveForm.vue`（稽核缺失文件表單＋任務區塊）、`PoamTaskCard.vue`（執行人員 chip 多選、改善期限、改善措施、帶入稽核建議）、`PoamSubmitDialog.vue`（守門清單／確認表）、`PoamImportPreview.vue`（預覽模式）。舊 `RoundPoamView.vue` 改一行導向新頁後退役。表單用語照雛形（稽核缺失、執行人員、改善期限、改善措施、已規劃／未規劃／待規劃），不放說明句（FR-142 D12 同）。禁 `border-left` ≥3px。

### Excel 契約（延伸 FR-141、FR-142，棒 4）

「改善計畫」頁只列判不符合的評估目標，一列一個任務；同一條評估目標的灰底欄合併儲存格、垂直置中，評估目標之間粗上框；凍結窗格在 S 欄：

| 欄 | 內容 | 匯入 |
|---|---|---|
| A（隱藏） | part_id | 比對鍵 |
| B～J（灰，合併） | FR-141 計畫欄：序號、控制項、實務要求、執行說明、評估目標、查核方法、受評人員、受評元件、稽核人員；D～J 群組預設收合 | 不讀 |
| K～Q（灰，合併） | FR-142 結果欄：稽核結果、佐證檔案、其他佐證、稽核發現、風險等級、風險說明、稽核建議 | 不讀 |
| R（隱藏） | job_uid | 比對鍵 |
| S～T（淺藍） | 任務、原指派人 | 不讀 |
| U（黃） | 執行人員：一格，多人以 `;` 分隔；預設原指派人；必須是專案成員 | 讀 |
| V（黃） | 改善期限：`YYYY-MM-DD`，日期驗證；必填 | 讀 |
| W（黃） | 改善措施；必填 | 讀 |

「可選清單」頁只留「執行人員（專案成員）」。匯入預覽：異動列標黃、原值劃掉；執行人員對不到成員、日期格式不對、同一任務多列執行人員或改善期限不一致 → 標紅「待確認」就地處理；全部處理完才能確認。匯出名「改善計畫工作表（Excel）」。範例：[`prototype/sample/FR-143-改善計畫範例.xlsx`](prototype/sample/FR-143-改善計畫範例.xlsx)（產生腳本 `make_sample_xlsx.py`）。

### 退卡與覆核輪（棒 5）

新檔 `app/flow_control/service/job_restart_service.py`：`restart_from_first_station(workflow_execution_id, due_date, assignee_user_ids, curr_user)`：
1. 取該流程全部站（`get_latest_station_jobs`），依 BPMN 順序找第一站。
2. 第一站 `reopen_job_execution`（進行中、清 end_time、通知）；其餘站 status 改 TODO、清 end_time；流程 status 改 PROCESSING。
3. 每一站 `due_at`＝`due_at_from_milestone_date(due_date)`、`due_at_manual=false`。
4. **第一站**的 `task_assignees` 改成執行人員清單（保留 `is_approver` 既有值）；後續站不動。
5. 寫 `job_execution_redo_log`（每站一筆）與系統留言（`build_redo_comment`，內容＝改善措施）。

`not_met_job_redo_service.redo_not_met_jobs` 改成逐 task 呼叫上式（來源從 `earliest_milestone_dates` 改讀 task 的 `props.job_uid`＋`target_date`＋`props.assignee_user_ids`）。`launch_reverify` 新任務：從第一站開始，`due_at` 與指派人同上。到期提醒（FR-140 P4）不動。

### 入口清單（棒 6）

BE 新讀模型 `app/readmodel/service/poam_dashboard_service.py`＋`infra/readmodel/audit/poam_dashboard_query.py`（照 `auditor_dashboard_*` 寫法）：條件＝登入者是該專案 manager、稽核次已結案且 POA&M 有 item；欄位見 D9；分頁、排序、篩選（狀態、稽核性質、標準、關鍵字）走後端。端點 `POST /grc/poams/my/list`（`RequestMetaSchema`）。FE 新頁 `src/views/project/MyPoamsView.vue`（表格，照 `MyTasksView` 的 lazy DataTable），路由 `/project/poam-manage`，名稱 `poam-manage`。路由 seed：`scripts/sql/<日期>-fr143-poam-manage-route.sql`（phase=seed：`ui_routes` 一列、`route_capabilities` 綁 `grc.audit.*` read=ALL，照 2026-04-27 先例）；守衛 `test/test_fr132_route_entry_registered.py` 要過。

---

## 端到端流程 {#flow 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'}}}%%
sequenceDiagram
    participant PM
    participant FE as 改善計畫頁
    participant BE as BE／套件
    participant Job as 任務流程
    PM->>FE: 側邊選單「改善計畫」→ 點輪次
    FE->>BE: GET /poam/objectives
    BE-->>FE: 不符合評估目標：稽核缺失＋底下任務
    PM->>FE: 逐任務設執行人員、改善期限、改善措施，儲存
    FE->>BE: PUT /poam/objective/<part_id>
    PM->>FE: 送出改善計畫
    FE->>BE: POST /poam/submit
    BE->>BE: 守門：有任務沒填齊 → 412 列原因
    BE->>BE: risk.status=remediating、poams.status=submitted、鎖定
    BE->>Job: 每條流程：第一站回進行中且指派人＝執行人員、後續站回待辦、每站 due_at＝改善期限、系統留言、通知
    BE->>BE: close_round（內部缺失改善站推進）
    BE-->>FE: 已送出
```

外稽同前半段；送出後 `close_round` 走「母輪→pending_reverify」；`launch_reverify` 新任務從第一站開始、改善期限與指派人照計畫。

---

## 端到端驗收 {#acceptance nav="驗收"}

1. **入口**：PM 帳號登入，側邊選單「改善計畫」，表格一專案一列（小字「共 N 份改善計畫 · 第 R 輪」），預設頁籤「待規劃」帶筆數、有待規劃的專案預設展開；點多份專案展開子列由舊到新、「目前」標在最急那次；搜稽核次文字命中自動展開標黃；只當 auditor 的帳號看不到或為空。
2. **規劃**：點一輪進改善計畫頁，左樹只有不符合的評估目標；點 AC.L1-b.1.iv [c]，上半文件表單顯示稽核發現、風險等級、稽核建議、查核方法、採用佐證；下半列出底下的流程各一塊（一條三站流程只有一塊，站名與狀態唯讀列在塊內）；加一位執行人員、設改善期限、按帶入稽核建議；儲存重整還在；左樹燈號部分規劃。
3. **共用**：同一任務在另一條評估目標出現時顯示共用提示，改改善期限兩處同步。
4. **守門**：還有任務沒填齊時按送出被擋，列出「主管檢核 未填：改善措施、改善期限」；補完後確認表列出每個任務、執行人員（註明新增誰）、改善期限；送出成功頁面唯讀，再呼叫寫入 API 回 409。
5. **退卡**：三站流程全完成後送出：第一站回進行中、後兩站回待辦、三站 `due_at` 都是計畫日期（台北 23:59:59）、第一站 `task_assignees` 等於執行人員、主管檢核與顧問檢核的指派人不變、第一站有系統留言寫改善措施、執行人員收到通知；「我的任務」看到第一站帶改善期限。
6. **Excel**：匯出 Excel，改善計畫頁一列一任務、計畫欄收合、評估目標欄合併；改兩列執行人員與改善期限、寫一個不是成員的名字匯回，預覽標黃與待確認，就地修正後確認，畫面更新。
7. **OSCAL**：匯出 OSCAL POA&M JSON，用 `~/.claude/skills/oscal-knowledge/references/json-schemas/oscal_poam_schema.json` 驗證通過；每個 task 有 `timing.on-date` 與 `responsible-roles`；`related-findings` 沒有 `finding-id`；`findings[]`／`risks[]` 非空。
8. **外稽**：外稽送出後輪次進待覆核；發起覆核輪，新任務從第一站開始、改善期限與指派人照計畫。

---

## 拆分表 {#cards nav="拆卡"}

Notion：母卡 CM-2840；子卡 1＝CM-2841、2＝CM-2842、3＝CM-2843、4＝CM-2844、5＝CM-2845、6＝CM-2846、7＝CM-2851。

| 卡 | 做什麼 | 碰哪裡 | 套件 | 依賴 | 驗收 |
|---|---|---|---|---|---|
| 1 | 匯出四處修正；`all_items_closed`→`all_items_planned`；task 存 `props.job_uid`／`assignee_user_ids`；`earliest_milestone_dates`／`milestones_by_control` 改回傳逐 task（job_uid、date、assignees、advice） | `pkg/jedi-oscal-v2/app/service/io/` 新 `poam_export_parts.py`；`pkg/jedi-compliance-audit/app/service/poam_app_service.py`（493）、`audit_round_pass_transitions.py:96` | 兩支 | — | 驗收 7 |
| 2 | `poam_objective_app_service.py`＋API、守門、鎖定、寫回任務、推進 | `app/flow_control/service/`、`api/project/routes/`、`api/project/__init__.py`、`common/code/` | — | 1 | 驗收 2、3、4 的 API 部分 |
| 3 | FE 改善計畫頁 | FE `src/views/project/RoundPoamPlanView.vue`、`src/components/grc/poam/`、router、api.js、i18n | — | 2 | 驗收 2、3、4 畫面部分 |
| 4 | Excel 一列一任務讀寫（合併、群組）、執行人員比對、預覽；FE 預覽模式 | `app/flow_control/service/poam_workbook_*.py`（新）；FE `PoamImportPreview.vue` | — | 1（FE 部分等 3） | 驗收 6 |
| 5 | `job_restart_service.py`；退卡改逐 task 第一站重跑、每站 due_at、指派人；覆核輪同 | `app/flow_control/service/not_met_job_redo_service.py`（126）、新檔；`pkg/jedi-compliance-audit/.../audit_round_app_service.py:543 launch_reverify` | jedi-compliance-audit | 1 | 驗收 5、8 |
| 6 | 入口清單讀模型、端點、FE 表格頁、路由 seed | `app/readmodel/`、`infra/readmodel/audit/`、`api/`、FE `MyPoamsView.vue`、`scripts/sql/` | — | 2 | 驗收 1 |
| 7 | 清單改依專案分組展開；`useAuditWorkGroups` 泛用化與稽核作業頁共用 | FE `MyPoamsView.vue`、`useAuditWorkGroups.js`、`my-poams.json` | — | 6 | 驗收 1 |

---

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

| # | 坑 | 影響 | 對策 |
|---|---|---|---|
| 1 | 現行退卡 `pick_latest` 只重開最新一站 | 多站流程退錯人；DEV 6 筆退卡紀錄全單站，測不出 | 棒 5 新 service 退第一站；驗收 5 用三站流程 |
| 2 | 期限只在站層，推出下一站不帶 | 後續站沒期限 | D12：退卡時全部站一次寫 |
| 3 | `revert_job` 帶操作者與輪次凍結守門 | 系統退卡直接呼叫會被擋 | 棒 5 另包，不呼叫 revert_job |
| 4 | `oscal_io_service.py` 1,697 行、`audit_round_app_service.py` 918 行、`RoundPoamView.vue` 890 行皆超標 | 不能往裡加 | 新開檔；原檔只加一行呼叫或退役 |
| 5 | task `assignee_user_id` 單值 | 多執行人員存不下 | props 存全部、欄位存第一人；匯出 responsible-roles 讀 props |
| 12 | 「任務」與「站」混用 | 雛形曾把三站畫成三塊，規格跟著寫錯 | 本文件「任務」一律指流程；站只唯讀顯示 |
| 6 | 執行人員對 party-uuid 查不到 | 匯出造假 uuid 不合規 | 查不到只寫 props 不寫 responsible-roles |
| 7 | 同一任務對多條評估目標 | 兩邊設定不一致 | PUT 時一併套用；Excel 多列不一致標待確認 |
| 8 | `related-findings` 私有鍵 `finding-id` | 官方 schema 驗證失敗 | 匯出剝掉，DB 留 |
| 9 | 送出後 PM 在抽屜改改善期限（FR-140 P5） | 計畫與任務漂移 | 維持 P5：手改標 manual、不回寫 POA&M；計畫鎖定不改 |
| 10 | 路由 seed 是 `phase=seed`，不是主線 active | 不需重產出貨基線 | 紀律段照實寫，runner 不要誤報 |
| 11 | 守門文案 `GRC_POAM_NOT_ALL_CLOSED` 被 FE i18n 引用 | 改文案要同步 FE | 棒 1 改 BE 文案、棒 3 同步 i18n |

