# Spec 2：階段抽象整合

> **狀態**：v2 含 §九 決議段（A1~A4 + B1~B2 + error code caveat）
> **整體脈絡**：`../README.md`
> **更新日期**：2026-05-12

---

## 一、目標

把目前 5 個既有 view（settings / overview / audit / poam / 待辦任務）改造成「BPMN UserTask 可推進」的階段；提供 stage 推進 API；UI 收口到 Banner。

---

## 二、需求摘要

來源：requirement.md §「想法（粗略）」§2、§3 末尾「上方功能按鈕 / Complete 按鈕 / 文案隨階段變化」

1. AP 上掛 1:1 main workflow_execution（BPMN 主流程實例）
2. 既有 5 個 view 接入「當前階段」概念，上方顯示 Banner
3. 使用者按 Banner「完成此階段」推進 BPMN 到下一節點
4. 推進前驗證主要角色權限 + 前置條件
5. 推進時觸發舊按鈕的副作用（原本「啟動專案 / 啟動稽核 / 提交稽核 / 完成改善」做的事）
6. EndEvent 觸發 AP 自動結案
7. 「執行任務」階段不綁頁面 — 待辦任務頁與總覽控制項導覽區塊靠 AP.status + 角色過濾資料

---

## 三、範圍

### In scope
- AP ↔ workflow_execution 1:1 關聯（schema 改動）
- Stage 推進 API（complete current stage）
- 前置條件 hook 框架（每階段物件可註冊）
- 副作用 handler 框架 — handler 本質上是呼叫 Spec 1 §3.2 定義的 4 個既有 API
- AP.status 與 stage 推進連動（接 §3.2 既有 API 自動處理）
- `FlowPhaseBanner` FE 元件
- 4 個 routed view 接入 Banner（settings / overview / audit / poam）
- **4 個推進按鈕拆下來搬 Banner**：
  - settings 頁的「啟動專案」
  - overview 右上角的「啟動稽核」（AP.status=active 時顯示那顆）
  - audit 頁的「提交稽核」
  - poam 頁的「完成改善」
- 待辦任務頁資料層過濾（既有「執行任務」邏輯不動，確認可 work）

### Out of scope（明確不做）
- 範本 CRUD（→ Spec 1）
- 建 AP 時選範本（→ Spec 3）
- 過渡期：暫用寫死的 default 範本給 AP，Spec 3 完成後改為使用者選擇
- **既有 4 個導航按鈕不動**（overview 右上角的「專案規劃 / 進入稽核 / 查看稽核 / 改善計畫」），沿用既有 `v-if` 邏輯
- **Overview shell 化重構**（→ Spec 3，本 spec 只在既有 overview 上掛 Banner）
- **Stage progress bar 互動式跳轉**（v1 進度條純預覽不可點）

---

## 四、Banner UI 結構

每個 routed view（settings / overview / audit / poam）上方掛 `FlowPhaseBanner`，內容：

```
┌──────────────────────────────────────────────────────────────┐
│ 目前在「執行任務」階段                                          │
│ ●━━━━●━━━○━━━○  (規劃 ✓ 執行 ◆ 稽核 改善)  ← 純預覽，不可點   │
│ 提示文字（隨 stage 變化，從 stage metadata 取）                  │
│                                              [▶ 啟動稽核]      │
└──────────────────────────────────────────────────────────────┘
```

| Banner 元素 | 來源 / 行為 |
|---|---|
| 階段名稱 | 從 stage `name_i18n` |
| 進度條 | 從 AP 的 BPMN snapshot 解析所有 stage 序列；當前 stage 高亮，過去 stage 標 ✓；**v1 不可點**（visualization only） |
| 提示文字 | 從 stage metadata（i18n key） |
| 推進按鈕 | 文字 = stage `complete_button_label_i18n`；顯示條件 = 使用者擁有 stage `default_main_roles`（或 BPMN UserTask 覆寫的 main_roles）|
| 前置條件未滿足 | 按鈕 disable + tooltip 顯示原因（如「尚有 12 / 50 任務未完成」） |

「執行任務」stage（stateful）只掛 Banner 在 overview 頁；其他三個 routed view（settings / audit / poam）各自掛。

---

## 五、task_execution 階段的「PM + 審查人員協作」設計

| 角色 | 在此階段做什麼 |
|---|---|
| `manager` (PM) | 看 task 完成進度 + 看 reviewer 的 review_mark 進度，自行決定何時按 Banner「啟動稽核」推進 |
| `reviewer` | 對控制項 / AO 用既有 `review_mark` 機制標記「審閱通過」（既有 API：`POST /grc/.../controls/<uid>/review-mark`、`POST /grc/.../ao/<uid>/review-mark`，不需新做）|
| `auditor` / `viewer` | 唯讀 |

Banner 上的輔助統計（v1 可選做、可延後）：

```
任務完成度：38 / 50 控制項
審閱通過：22 / 50 控制項  (reviewer 已標記)
```

「review_mark 是否強制 gate」**v1 不做強制** — PM 可自由判斷推進；既有 `launch-audit` API 的 `force=false` 邏輯已可擋未完成任務並彈 confirm dialog。

---

## 六、高階任務拆解

### 後端
- [ ] 設計 AP ↔ workflow_execution 1:1 關聯方案（待 raymond 決：在 `assessment_plans` 加欄位 / 開 mapping 表）
- [ ] Stage 推進 API（驗證主要角色 + 驗證前置條件 + 呼叫對應 §3.2 既有 API + 推進 BPMN）
- [ ] 前置條件 hook 框架（每階段物件可註冊一個 precondition）
- [ ] 副作用 handler 框架（每階段物件可註冊一個 on_complete，內容是呼叫既有 OSCAL service）
- [ ] **handler 對映實作**（不重做既有 API）：
  - [ ] planning on_complete → 呼叫既有 `OscalAuditService.activate` (進 `active`)
  - [ ] task_execution on_complete → 呼叫既有 `OscalAuditService.launch_audit` (進 `auditing`)
  - [ ] audit on_complete → 呼叫既有 `OscalAuditService.confirm_audit` (進 `remediation` / `closed`)
  - [ ] poam on_complete → 呼叫既有 `OscalAuditService.close_round` (進 `closed`)
- [ ] EndEvent → AP 自動 closed（既有 close_round 已會做，BPMN 端確認 endpoint reach）
- [ ] 待辦任務頁「執行任務」資料過濾邏輯確認（既有 `get_user_task_queue` SP 是否要改）

### 前端
- [ ] `FlowPhaseBanner` 元件（§四 結構）
- [ ] 4 個 routed view（settings / overview / audit / poam）掛 Banner
- [ ] **4 個既有推進按鈕拆掉**：
  - [ ] settings 頁的「啟動專案」
  - [ ] overview 右上角的「啟動稽核」（line 1180~1184）
  - [ ] audit 頁的「提交稽核」
  - [ ] poam 頁的「完成改善」
- [ ] 既有 4 個導航按鈕（專案規劃 / 進入稽核 / 查看稽核 / 改善計畫）**不動**

---

## 七、對外依賴

- 依賴 Spec 1：階段物件 schema 與 seed、`GET /flow-engine/stage-objects`
- 過渡期：暫用寫死的 default 範本給 AP，等 Spec 3 完成後改為使用者選擇

---

## 八、開放問題（已決議，紀錄保留供溯源）

> 全部於 2026-05-12 收斂；具體決議見 §九。

- [x] **AP ↔ workflow_execution 關聯放哪** → §9.1 採 `compliance.assessment_plan_extensions` 延伸表
- [x] **前置條件不滿足時的 UX** → §9.2 disable + tooltip + manager force override
- [x] **副作用搬家可能跨模組 import** → §9.3 Hook Interface + Registry
- [x] **缺失改善 → 稽核 loop back 的推進路徑** → §9.4 v1 直接結案，Dialog 二擇一留 Spec 3
- [x] **既有「啟動專案」之前的 preparing 狀態** → §9.5 沿用既有 preparing，AP 建立後停在 planning UserTask

---

## 九、決議（2026-05-12）

對應 §八 5 條 + README §四 跨 spec 2 條，Phase 3 plan 前的最終收斂。

### 9.1 AP ↔ workflow_execution 1:1 — 採延伸表

**決議**：新建 `compliance.assessment_plan_extensions` 表（mirror 既有 `compliance.project_extensions` pattern），**不修改 jedi-oscal `oscal.assessment_plans`**。

**Schema 草案**（細節 plan.md 落地）：

```sql
CREATE TABLE compliance.assessment_plan_extensions (
    id                           SERIAL PRIMARY KEY,
    assessment_plan_id           INTEGER UNIQUE NOT NULL,            -- FK to oscal.assessment_plans(id), 1:1
    workflow_execution_uid       UUID,                                -- BPMN main workflow execution（AP 建立時同步寫入）
    flow_template_snapshot_uid   UUID,                                -- AP 採用的範本 snapshot（Spec 2 暫用 hardcode default，Spec 3 由 user 選）
    created_at                   TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    updated_at                   TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    created_user                 VARCHAR(50),
    updated_user                 VARCHAR(50),
    CONSTRAINT fk_ape_assessment_plan
        FOREIGN KEY (assessment_plan_id)
        REFERENCES oscal.assessment_plans(id)
        ON DELETE CASCADE
);
CREATE INDEX ix_ape_workflow_execution_uid ON compliance.assessment_plan_extensions(workflow_execution_uid);
GRANT SELECT, INSERT, UPDATE, DELETE ON compliance.assessment_plan_extensions TO cm_app;
GRANT USAGE, SELECT ON SEQUENCE compliance.assessment_plan_extensions_id_seq TO cm_app;
```

**為什麼**：
- jedi-oscal 套件邊界保持乾淨（不動共用套件）
- 與 `compliance.project_extensions` pattern 完全一致，新開發者好理解
- 1:1 由 UNIQUE constraint 強制；cross-schema FK 在 PostgreSQL 合法

**未來反悔條件**：若 jedi-oscal owner 同意納編，可移欄位回主表（migration 直接搬欄位 + drop ext）。

**淘汰的暫態 patch**：未追蹤的 `domain/flow_engine/repository/i_ext_assessment_plan_repo.py` 與 `infra/.../ext_assessment_plan_repo_impl.py` 走 cross-schema raw SQL 直接更新 `oscal.assessment_plans.status`，Spec 2 接手後改走 grc 模組既有 `OscalAuditService.close_round`（via hook handler），這兩個檔在 Phase 5 刪除。

### 9.2 前置條件 UX — disable + tooltip + manager force override

**決議**：Banner 推進按鈕預設 UX：

| 情境 | 按鈕行為 | 訊息來源 |
|---|---|---|
| 前置條件 pass | 可點 | — |
| 前置條件 fail | **disabled + tooltip** 顯示原因 | precondition function 回傳的 `(passed=False, reason_i18n_key, context)` |
| Manager 角色強制推進 | 點下 → 跳 ConfirmDialog 列原因 → 確認後送 `force=true` | 沿用 `launch-audit` 既有 pattern |

**Precondition 簽名**：

```python
@dataclass
class PreconditionResult:
    passed: bool
    reason_i18n_key: Optional[str] = None   # e.g. "flow_engine.precondition.tasks_not_complete"
    context: Optional[dict] = None           # e.g. {"completed": 38, "total": 50}

class IStagePreconditionCheck(ABC):
    @property
    @abstractmethod
    def key(self) -> str: ...                # 對應 stage_object.precondition_key

    @abstractmethod
    def check(self, ap_uid: str, project_uid: str, ctx: dict) -> PreconditionResult: ...
```

**為什麼**：
- 與既有 launch-audit `force=false` 設計一致，FE 不需要學新 pattern
- disable + tooltip 直接看到原因，比 toast/dialog 不打斷流程

### 9.3 副作用 handler — Hook Interface + Registry

**決議**：採乾淨 DDD 方案，**flow_engine 不知道 OSCAL 存在**。

**架構**：

```
domain/flow_engine/service/
├── stage_completion_registry.py     ← Registry singleton
│   ├── IStageCompletionHandler      ← Interface（key + execute）
│   ├── IStagePreconditionCheck      ← Interface（key + check）
│   ├── PreconditionResult           ← Dataclass
│   └── StageRegistry                ← register / get_handler / get_precondition

app/flow_engine/service/
└── stage_advance_service.py         ← 從 registry 查 handler 呼叫，不知 OSCAL 細節

app/grc/service/
└── oscal_stage_handlers.py          ← 4 個 Handler class（OSCAL business logic）
    ├── PlanningOnComplete           → 呼叫 OscalAuditService.activate
    ├── TaskExecutionOnComplete      → 呼叫 OscalAuditService.launch_audit
    ├── AuditOnComplete              → 呼叫 OscalAuditService.confirm_audit
    └── PoamOnComplete               → 呼叫 OscalAuditService.close_round

app/grc/service/
└── oscal_stage_preconditions.py     ← 3 個 Precondition class（task_execution / audit / poam）

di_containers/grc/grc_containers.py  ← 啟動時 register handlers + preconditions 到 registry
```

**Stage object schema 對映**（已在 spec 1 定義）：

| stage code | complete_handler_key | precondition_key（v1 提案） |
|---|---|---|
| `planning` | `oscal.planning.activate` | （v1 無，永遠 pass） |
| `task_execution` | `oscal.task_execution.launch_audit` | `oscal.tasks_threshold_check`（force 可 bypass） |
| `audit` | `oscal.audit.confirm_audit` | `oscal.all_controls_verdicted_with_finding` |
| `poam` | `oscal.poam.close_round` | `oscal.all_poam_closed` |

**為什麼**：
- `flow_engine` 模組仍可獨立給其他 domain 用（非 OSCAL 業務）
- 新增 stage 業務時：寫一個 Handler class + 註冊；不動 flow_engine
- 跨模組 import 只在 DI container（合法位置）

**未來反悔條件**：若 registry 只剩 4 個 handler 永遠不擴張，可塌成直接 import；但前提是確認不再有其他 domain 接 flow_engine。

### 9.4 Loop back v1 簡化版

**決議**：

| 項目 | Spec 2 v1 落地 |
|---|---|
| `builtin-full-audit` 範本 BPMN | 保留 `audit ←─ poam` gateway 結構（範本層不動） |
| ExclusiveGateway condition | `${pm_decides_loopback == true}` placeholder |
| Runtime PM Dialog 二擇一 | **不做**（留 Spec 3+） |
| poam on_complete 實際走 | `OscalAuditService.close_round` 直接結案（走 gateway default flow） |
| 顯示效果 | 看到 poam 完成 → AP closed；audit ←─ poam 路徑 BPMN 圖上可見但 runtime 不會走 |

**為什麼**：避免 Spec 2 範圍爆炸；客戶實際遇到 loop back 需求時再加 Dialog。既有 `close_round` 邏輯穩定可用。

**未來反悔條件**：客戶要求多輪 audit iteration 時，加：
- FE：poam Banner 點「完成改善」前先跳 Dialog
- BE：`stage_advance` 接受 `loopback_decision: 'close' | 'reaudit'` param，據此設 BPMN process variable

### 9.5 AP 建立後預設停在 planning UserTask

**決議**：

- AP 建立流程結尾 **同步啟動** workflow_execution（依 `flow_template_snapshot_uid` clone 範本 → 建立 execution → 推進到第一個 UserTask）
- 第一個 UserTask = `planning` stage（builtin 3 個範本第一個 UserTask 都是 planning）
- AP.status 維持 `preparing` 直到 user 按 Banner「啟動專案」
- planning on_complete → `OscalAuditService.activate` → AP.status: `preparing` → `active`

**為什麼**：與既有 4 個推進 API + spec 1 設計一致；不引入新 AP.status。

### 9.6 launch_new_round 範本預設值 — 專案層級 default

**決議**：

- **Spec 2 範圍**：`compliance.assessment_plan_extensions.flow_template_snapshot_uid` 預留欄位；
  AP 建立時 hardcode 套用「完整稽核流程」builtin 範本（Spec 2 沒 UI 讓 user 選）
- **Spec 3 範圍**：
  - `compliance.project_extensions` 加 `default_flow_template_uid` 欄位
  - 建專案時選範本 → 存 project default
  - 建 AP 時自動帶入 project default，可在 wizard override
  - launch_new_round 時沿用上一輪的 `flow_template_snapshot_uid`（不重選，與 OSCAL 控制項結構 clone 邏輯一致）

**為什麼**：兼顧連貫與彈性；Spec 2 不負擔 wizard 設計。

### 9.7 Error code 序號（caveat）

> 既有 `GRC_412010` 已被 `GRC_AP_NOT_CLOSED` 佔用，Spec 2 編號往後挪。

| 用途 | 編號 | 訊息 |
|---|---|---|
| Stage 推進前置條件未滿足 | `GRC_412021` | 「{reason_i18n_key}」（動態 i18n key 透過 context fill） |
| 使用者不具備此 stage 主要角色 | `GRC_403050` | 「使用者不具備此階段推進權限」 |
| BPMN 無下一個節點 / 推進邏輯異常 | `GRC_400050` | 「流程推進失敗：找不到下一節點」 |
| AP 未綁定 workflow_execution | `GRC_404029` | 「稽核計畫未綁定流程實例」 |
| Stage 推進 handler key 未註冊 | `GRC_400051` | 「stage handler 未註冊」 |
| Stage object 未綁定到 BPMN UserTask | `GRC_400052` | 「BPMN UserTask 未綁定階段物件」 |

新增 6 個 error code 寫入 `common/code/grc_error_code.py`。

---

## 十、Reconciliation — 實作偏離設計的紀錄（Phase B/D smoke 期間累積，2026-05-13）

> 本段紀錄 plan v1 → 實作落地過程的偏差與成因，給未來讀者保留決策軌跡。
> 對應 issue `docs/issues/resolved/2026-05-13-spec2-step22-snapshot-pattern.md`
> + changelog `docs/changelog/2026-05-13-fix-spec2-step22-snapshot-and-corrections.md`。

### 10.1 Handler key 命名移除 namespace 前綴

**Plan v1**：`oscal.planning.activate` / `oscal.task_execution.launch_audit` …
**實際**：seed 為 `activate_project` / `launch_audit` / `confirm_audit` / `close_round`（無前綴）
**為何**：BE Phase B.1 落地時 stage_objects.complete_handler_key 直接用 service method 名稱，FE 也不分辨，namespace 前綴沒實質作用。

### 10.2 `flow_templates` 沒 stable `code` 欄位 → 用 name lookup

**Plan v1**：`flow_template_app_service.get_by_code("builtin-full-audit")`
**實際**：`flow_template_domain_service.get_default_main_workflow_template()` 內 hardcode name = "完整稽核流程"，透過 `get_builtin_by_name` 查
**為何**：spec 1 `flow_templates` 表沒 `code` 欄位，加 column 屬於 spec 1 schema 變動超出 spec 2 範圍。單點 hardcode 在 domain service，未來加 `code` 欄位後改一行即可。

### 10.3 `complete_main_workflow_job` 平行 method — 不重用既有 `complete_job`

**Plan v1**：直接 call engine 既有 `complete_job(workflow_execution_uid, job_id, ...)`
**實際**：主專案 `workflow_execution_service.py` 加新 method `complete_main_workflow_job`
**為何**：既有 `complete_job` 假設 workflow_execution 對應到單一 AP task（透過 assessment_plan_task_workflow_execution_mapping），會跑 task-level notification / project status update 等副作用。Main workflow 是 AP-level 不在 mapping 內 → 必須跳過那些副作用。

### 10.4 EndEvent handler 設計改為「handler 為 single source of truth」

**Plan v1**：BPMN engine `_on_end_event_reached` 內 `ExtAssessmentPlanRepoImpl` 寫 raw SQL UPDATE AP.status='closed'
**實際**：刪除 `ExtAssessmentPlanRepoImpl` + `_on_end_event_reached` 不動 AP 狀態，AP 結案完全由 confirm_audit handler 處理
**為何**：避免「兩條路徑同時改 AP 狀態」造成幽靈轉換；handler 是 single source of truth。Plan §B.6 已落地。

### 10.5 Plan §D.1 view 路徑寫錯 — 啟動專案按鈕在 ProjectPlanningView 不在 ProjectSettingsView

**Plan v1**：`src/views/grc/ProjectSettingsView.vue` 拆「啟動專案」按鈕
**實際**：實際按鈕在 `src/views/project/ProjectPlanningView.vue:1372`
**為何**：ProjectSettingsView 是專案基本資料編輯頁，沒有 launch 按鈕；plan 文件落地前該驗 grep。Phase D handoff prompt 已修正。

### 10.6 Snapshot pattern — Step 22 範本表 bridge

**Plan v1（§B.5）**：`start_workflow_execution(template_id=spec1_template.id, ...)`，假設 spec1 `compliance.flow_templates` 跟 engine `public.workflow_templates` 雙表 id 可直接互通
**實際**：兩表完全獨立（min engine id = 7247，spec1 id = 1-6）→ AttributeError。改為 **per-AP snapshot pattern**：建 AP 時 clone master 內容到 engine 表產生新 row（凍結），engine row 的 `source_template_uid` 記回 master uid 溯源
**為何**：
- 合規 traceability：「這個 AP 用什麼版本 workflow 稽核」要永遠回得出來
- Admin 編輯 master 不影響 in-flight AP（snapshot 凍結）
- `ape.flow_template_snapshot_uid` 欄位名本來就是 snapshot 語意（spec 設計時想好的，實作偷工）

詳見 `docs/analysis/2026-05-13-flow-template-snapshot-pattern.md`。

### 10.7 主專案 `workflow_execution_service.py` fork pattern — 改套件對 spec 2 路徑無效

**Plan v1**：改 engine 套件 `start_workflow_execution` 就 done
**實際**：主專案 `app/flow_engine/service/workflow_execution_service.py` 是 fork-and-extend（14 個 method 跟套件同名 fork，加 17 個額外 method），Step 22 走的是主專案 wrapper 不是套件。改套件對 spec 2 無效，**要改兩處**。
**為何**：spec 1 / 既有 audit lifecycle 設計時對 engine 加了一堆業務客製（notification / project status / AP-task mapping），fork 出來自己管。長期 tech debt（建議重構為「繼承套件 class + override」），本 spec 不處理。

### 10.8 Engine `start_workflow_execution` 假設 main process 只放 CallActivity

**Plan v1**：相信 engine `start_workflow_execution` 會 instantiate 第一個 UserTask
**實際**：engine 原邏輯只處理 sub_workflow CallActivity 的 first jobs；「完整稽核流程」master BPMN 把 UserTask 直接寫在 main process（非 CallActivity 結構）→ engine 沒 instantiate 任何 UserTask
**為何**：BPMN 標準允許 main process 內直接放 UserTask（線性 4-stage），engine 既有實作太特殊化（assume 只放 CallActivity）。套件層 fix `77bfc83` 補 main process UserTask 邏輯作為 engine 增強；主專案 wrapper 也補同樣邏輯（commit `f1f83bc`）真正生效這個。

### 10.9 BPMN gateway condition_param Camunda format mismatch

**Plan v1**：`condition_param={"has_findings": True}` 丟給 engine
**實際**：engine `_parse_condition_param_to_string` 把 Python `True` 字串化成 `"has_findings == True"`，但 BPMN conditionExpression 是 `${has_findings == true}`（Camunda 標準，`${}` 包覆 + lowercase）— exact string match 永遠 fail，gateway 都走 default flow（end）
**為何**：engine 既有 string-match 設計脆弱（不是 eval expression），spec 2 是第一個用 gateway 的 use case 才暴出來。套件層 fix `02713bc` 改輸出 Camunda 標準。

### 10.10 close_round 接管「發起覆核」副作用

**Plan v1**：close_round 只設 `ar_data.completed_at`，AP 維持 remediation，等 user 點「發起覆核」按鈕（call `launch_audit` force=True）→ AP→auditing + 建新 round
**實際**：Banner 拆掉「發起覆核」按鈕（Phase D.4 commit `c535af6`），close_round handler 必須接管覆核模式 4 個副作用：建新 ar_data run_no+1 + 複製 ar_controls（POA&M 對應 reset + 其他繼承）+ AP→auditing + 回傳 status="auditing"
**為何**：Banner 「完成改善」是唯一 user 推進入口，close_round 必須對齊 BPMN poam → audit loop back 的語意。Commit `353e9fc`。

### 10.11 Planning 加 `all_tasks_assigned` precondition（plan 沒列）

**Plan v1**：Planning stage `precondition_key = NULL`（無檢查）
**實際**：補 `all_tasks_assigned` — 啟動專案前要先把任務全指派
**為何**：合規 use case 上「啟動 = 任務該都已分派」是基本要求；plan 設計時遺漏（task_execution / audit / poam 都有檢查獨缺 planning）。Commit `2538e74`。

### 10.12 Banner 在多 view 需要 sub-action 觸發 refresh

**Plan v1**：Banner onMounted + props 變更時 fetchStageInfo 即可
**實際**：sub-action（verdict 變更 / POA&M 結案）會改 precondition state，但 Banner 不知道 → 按鈕保持 disabled，要重整頁才會 enable
**為何**：Banner 用 `defineExpose` 暴露 `fetchStageInfo`，parent view 用 ref 在 sub-action callback 內手動 trigger。AuditReviewView (`98d5aac`) / PoamView (`f875aa3`) 加 ref pattern。ProjectPlanningView / ProjectAuditorOverview 目前不需要（無 sub-action 改 precondition），未來補 Planning all_tasks_assigned 跨任務頁面 refresh 時要補同樣 pattern。

### 10.13 Banner 推進按鈕加二次確認 ConfirmDialog

**Plan v1**：只有 manager force / handler warning 場景開 ConfirmDialog，正常 path 直接 advance
**實際**：所有 advance path 都先彈 ConfirmDialog（commit `72fa8b7`）
**為何**：階段推進不可復原，正常 path 也該二次確認避免誤觸發。

### 10.14 Banner 結案後整塊隱藏

**Plan v1**：workflow 結案後 Banner 顯示「目前階段 —」空殼 row
**實際**：root div 加 `v-if="!isFlowClosed"` 整塊不渲染（commit `899bc77`）
**為何**：UX 上結案後不該有空殼 banner 佔位。

### 10.15 角色 gate — Overview 「進入稽核」/「改善計畫」按鈕

**Plan v1**：plan 沒明確規範角色 gate（只在 Banner 推進按鈕走 main_roles）
**實際**：Overview 內：
- 「進入稽核」(auditing) 只 manager 可見，其他角色看「檢視稽核結果」（commit `f8233a0`）
- 「改善計畫」(remediation/closed) 只 manager 可見（commit `1975d1a`）
**為何**：spec 2 預設「推進按鈕」是主要角色專屬，但 Overview 的導航按鈕 plan 沒提，user 測試時點出來。`btn_view_audit` 條件擴大成包含 auditing 階段給非 manager 用。

### 10.16 dev path mode 而不是套件 publish

**Plan v1**：套件改動 bump 0.0.27 → 0.0.28 + 推 Nexus + 主專案 `poetry update`
**實際**：dev 階段用 `pip install -e` editable + `pyproject.toml` 改 path dep，**不 bump version**；feature 完成 user 指示才一次性發版
**為何**：dev 期間多輪調整，每次 bump + 推 Nexus + poetry update 成本太高（poetry 卡 google-ai SAT 解析數十分鐘）。CLAUDE.md 已加「外部套件異動規範」段落化。

---

## 十一、文件版本

| 版本 | 日期 | 變更 |
|---|---|---|
| v1 | 2026-05-12 | 初版（§一 ~ §八）|
| v2 | 2026-05-12 | 加 §九 決議段（7 個議題） |
| v3 | 2026-05-13 | 加 §十 Reconciliation（16 條落地偏差）|
