FR-038 Round Flow FE 遷移 — 換 session Handoff(2026-06-17)

項目 內容
緣由 接續 round-flow 再整合:把專案生命週期 FE 從舊 AP-centric 引擎逐頁遷到 v2 audit_round + living/round SSP
BE branch feature/oscal-refactor勿切 branch;branch 不對停下問 user)
FE branch 同名 feature/oscal-refactor~/Projects/Billows/Audit-Manager/compliance-manager-fe/
下一棒主任務 專案規劃頁 ProjectPlanningView 資料沒帶出來 → 比照總覽遷到 v2 SSP control-tree
push 狀態 BE + FE 都有未 push commits → push 等 user 明示
驗證狀態 本弧全部已 user 手測通過(含 per-AO 任務 / get_job drawer,2026-06-17)

🧭 原始需求 / WHY(先懂這個再動 code)

大圖:FR-038 把 OSCAL 從 jedi_oscal v1 整碗換 v2。本弧處理 專案生命週期 FE 逐頁從舊 AP 引擎遷到 v2 audit_round。GRC 業務層在 Wave 2A 被大量 dark,這弧一頁一頁把資料源接回 v2。

這條線的需求(user 對話拍板)

  • 專案總覽 / 規劃頁的「控制項 + 現況說明 + 任務」資料源,依是否進入稽核階段切換:
    • 未進稽核階段(planning / 規劃)→ 從「主專案 living SSP」拉資料(建專案 clone 的脫鉤公版,錨在 compliance.project_extensions.living_ssp_id)。
    • 進入稽核階段後 → 從「該 round 的 frozen SSP」拉資料 + 編輯(如果可以)launch_audit 時 snapshot living SSP → project_audit_rounds.ssp_id,邊界③)。
  • 此切換是 OSCAL/NIST 800-53A 的 Prepare(規劃補 SSP + 收證據)vs Assess(稽核)分界。設計依據見 round-flow-engine-reintegration-design.md(D5 stage 模型 / 邊界③ snapshot / status 對照表)。

SSP-source 切換在 FE 怎麼自然落地(關鍵): 總覽 / 規劃頁的 sspUid computed 都是

sspUid = currentRound.ssp_uid  ??  project.ssp.id  ??  project.ssp_uid
  • planning 輪round.ssp_id 為 NULL(snapshot 還沒做)→ fallback 到 project.ssp.id(= living SSP uuid,BE get_project 已回,見下)→ 自動拉 living SSP
  • launch_audit 後round.ssp_id 有值(frozen snapshot)→ currentRound.ssp_uid 有值 → 自動拉該 round frozen SSP

所以「同一段 sspUid 邏輯」就涵蓋兩階段,不需 if/else 判 stage。規劃頁照抄總覽即可。

冷接自檢 4 問(答不出回去讀設計)

  1. planning 階段控制項 / 現況說明從哪拉?(→ 主專案 living SSP,project_extensions.living_ssp_id
  2. 何時切換成 round 的 SSP?(→ launch_audit snapshot 後,round.ssp_id 有值)
  3. FE 怎麼自動切換、不用判 stage?(→ sspUid = round.ssp_uid ?? project.ssp.id,planning 輪 round.ssp_uid 為 null 自動 fallback)
  4. 規劃頁該照誰實作?(→ 剛遷好的 ProjectAuditorOverview,§3 參考實作)

§0 接手讀序(按序)

  1. 🔒 本文件「原始需求 WHY」全讀
  2. round-flow-engine-reintegration-design.md §1~§4(D5 stage / 邊界③ / status 對照)
  3. §3 參考實作:ProjectAuditorOverview.vue 的 round-scoped 遷移(已完成,照它做規劃頁)
  4. §1 本 session 完成清單 + §4 踩過的雷

§1 本 session 完成(BE + FE,已 commit)

1.1 GRC 列表 / 刪除 / 建專案修補

commit 內容
49aca1ab 批次刪除掛掉 — batch_delete_projects@transaction
9a617180 建專案順帶建第一輪 initial + 綁流程範本(D6, Task 8b)
901d173(FE) 建專案後導到 round-scoped 總覽(project-auditor-overview-round + first_round_uid

1.2 輪次列表(ProjectApListView)改撈 audit_round

commit 內容
e200b01(FE) /project/projects/:id 列表來源 AP→POST /projects/<id>/audit-rounds/list;建輪次 / launch-audit / launch-reverify;整列可點進總覽

1.3 專案總覽 round-scoped + 控制項樹 + 現況說明(參考實作

commit 內容
03fb83bf get_project_by_uid 從 dark 重建(v2)— 修總覽「找不到專案」;回 living SSP uuidproject_extensions.living_ssp_idproject.ssp.id
fbebdee(FE) 總覽入口吃 roundUid(route project-auditor-overview-round = /project/projects/:id/round/:roundUid);Banner retarget round-scoped(StageService/FlowPhaseBanner/audit-round/<roundUid>/stage/{info,advance}
98b33fc3 新 BE 端點 GET /ssp/<ssp_uid>/control-tree:v2 catalog(群組→控制項→AO part)+ SSP 現況說明(implemented_requirements + statements)組樹;防重複 IR(偏好有描述那筆)
c014197(FE) 總覽 fetchGroups 改吃 /ssp/<sspUid>/control-treefetchControlDetail 從已載入控制項取

1.4 per-AO 專屬流程 + AO 任務 + get_job(對齊 V1 Step 8)✅ 已驗(2026-06-17)

commit 內容
d8c1d4fe project_start._generate_prep_jobs 重寫:每 AO 用自己的 workflow-template-uid(資源庫 _init_ao_workflows 寫進 cloned catalog part.props)clone 一份定版source_template_uid 指原始),回填 part.props 指向 cloned uid;get_control_tree 補 AO jobs(cloned wf uid → workflow_executions → job_executions);get_job 從 dark 重建(最小 GrcJobEntity)+ route None→404 guard

✅ user 手測通過(重啟 BE + 建新專案 → AO 顯示「收證據」任務 + 點任務 drawer 正常開)。 既有舊專案(舊單一 template、part.props 未回填)不會顯示 per-AO 任務 → 測試一律用專案。


§2 下一棒主任務:專案規劃頁 ProjectPlanningView 資料沒帶出來

症狀/project/projects/<id>/ap/<apUid>/planning 整頁資料沒帶出來(控制項 / 現況說明 / SSP)。

root cause(同總覽舊版)ProjectPlanningView.vue 仍是 AP-centric:

  • route.params.apUid + apSegment = /ap/<apUid>,打舊 dark 的 /grc/.../control-groups 引擎 → 空。
  • sspUid computed 已是對的 pattern(currentAp.ssp_uid ?? project.ssp?.id ?? project.ssp_uid),但 project.ssp 要 BE get_project 回(已修,03fb83bf)+ 入口要改吃 roundUid。

做法 = 照 §3 參考實作(總覽剛遷好的那套)

  1. route / 入口:規劃頁加 round-scoped route(/project/projects/:id/round/:roundUid/planning,名 project-planning-round),或讓現有 view 同時吃 route.params.roundUid(總覽用 isRoundRoute + selectedApUid = roundUid ?? apUid 雙吃法,見 ProjectAuditorOverview.vue:44)。總覽 / 列表的「規劃」按鈕導過去帶 roundUid。
  2. 控制項 / 現況說明:把規劃頁抓控制項的地方從 /grc/.../control-groups 改吃 GET /ssp/<sspUid>/control-tree(總覽 fetchGroups 範本,ProjectAuditorOverview.vue 內)。SSP 現況說明編輯沿用既有 SspService/ssp/<uid>/control-implementation/...,是活的、可寫)。
  3. sspUid 自動切換sspUid computed 保持 round.ssp_uid ?? project.ssp.id——planning 輪自動拉 living SSP、launch_audit 後自動拉 round frozen SSP(見 WHY 節)。編輯:living SSP 階段可寫(/ssp/<living_uid>/control-implementation PUT,Upsert 活的);round frozen 階段「如果可以」可寫(frozen 是否唯讀依設計,design §4.2 deep_clone_ssp 是可寫副本,但稽核階段是否鎖定待 user 確認)。

注意(同總覽踩過)

  • 覆核狀態 / 任務指派 = AR 層 / 指派 UI(FE Wave 3)概念,planning 規劃頁不一定要。
  • 規劃頁若也掛 <FlowPhaseBanner>,改傳 :round-uid(Banner 已支援 roundUid 優先、apUid deprecated fallback)。

§3 參考實作(照這個做規劃頁)

ProjectAuditorOverview.vue(FE,剛遷好) 是規劃頁的範本,關鍵段:

  • ProjectAuditorOverview.vue:44isRoundRoute + selectedApUid = roundUid ?? apUid 雙吃入口
  • ProjectAuditorOverview.vue fetchApList() — round route 時改撈 audit-rounds/list 並 map 成 header/dropdown shape
  • ProjectAuditorOverview.vue fetchGroups() — 改吃 GET /ssp/<sspUid>/control-tree,populate groups + controlsMap + syncControlMaps
  • sspUid computed — selectedAp.ssp_uid ?? project.ssp?.id ?? project.ssp_uid

BE 端點(都活的)

  • GET /ssp/<ssp_uid>/control-tree — 群組→控制項→AO(含現況說明 + AO 任務)
  • GET/PUT /ssp/<ssp_uid>/control-implementation/<control_identifier> — 單控制項現況(讀/Upsert)
  • PUT /ssp/<ssp_uid>/control-implementation/<ctrl>/objective/<stmt> — AO 層現況
  • GET /grc/project/<uid>ProjectResponseSchema)回 project.ssp = {id: living_ssp_uuid}

§4 本 session 踩過的雷(別重蹈)

  1. GRC 業務層 Wave 2A 大量 darkget_project_by_uid / get_job / grc_job_repo 多處 return None/[](標 FR-038 2A)。改 GRC read 前先 grep dark 標記,dark 的就比照 list_projects 重建(查 v2 表 + 套可見性 + enrich)。
  2. 重複 IRproject_start 同時「clone 範本 SSP 帶描述 IR」+「_init_ssp_control_implementations 建空白 IR」→ 每控制項兩筆 IR(一有描述、一空白)。讀取路徑要偏好有描述那筆get_control_tree / _ir_for_control 已做)。root cause(project_start 重複建 IR)待獨立修 + 既有資料清理——見 §6 follow-up。
  3. per-AO 流程 scope 不靠 schema:prep job 的 workflow_execution_control_mappings 只有 control_id + ao_part_id(無 project_id),跨專案同 catalog 會撞。改用「per-project cloned template 專案唯一」當 scope(part.props.workflow-template-uid 回填 cloned uid → 該 template 的 workflow_execution 即本專案的)。
  4. flow_engine 表在 compliance schemaworkflow_templates / workflow_executions / job_executions / task_assignees)。
  5. api/oscal/__init__.py 與 user in-flight 糾纏:該檔同時有「我的 SspControlTreeRoute」+「user 的 ModuleFrameTemplateSspResource」→ commit 時用 git apply --cached 切自己的 hunk(互動式 git add -p 在此環境不可用)。
  6. BE 無 hot reload:改 service/repo 後提醒 user 重啟(user 自己起服務)。

§5 Pre-flight + Verify(下個 session 開工必跑)

cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                    # 應 feature/oscal-refactor
git log --oneline -8 | cat                   # 對照 §1 commit 清單
git status --short | grep -vE '^\?\? docs/'  # 應只剩 user in-flight(api/oscal/__init__.py、module_frame/* 等)
# import smoke(sandbox 無法完整 boot)
set -a; source .env; set +a; export GITLAB_API_VERSION=4 GITLAB_URL=x GITLAB_PRIVATE_TOKEN=x GITHUB_PRIVATE_TOKEN=x
.venv/bin/python -c "import app.oscal.service.ssp_control_implementation_service, app.project.service.project_start_app_service; print('import OK')"

Verify 本期成果(重啟 BE 後,建新專案 → 進 round 總覽):

  • 控制項導航列出群組 + 控制項數(非 0 群組)
  • 點控制項 → 右側 AO 清單 + 現況說明(點擊不消失)
  • 展開 AO → 「收證據」任務(TODO)→ 點任務 drawer 開不報錯
  • BE log:generate_prep_jobs ... jobs=N cloned_templates=N (per-AO 專屬流程,對齊 V1 Step 8)

§6 已知 follow-up(本期未做,獨立處理)

  • project_start 重複建 IR root cause:clone 範本 SSP(帶 IR)+ _init_ssp_control_implementations(建空白 IR)兩邊都建 → 每控制項兩筆 IR。需改 project_start 擇一 + 寫既有專案空白重複 IR 清理 SQL。(本期只在讀取路徑 robust)
  • round frozen SSP 稽核階段是否唯讀:design §4.2 deep_clone_ssp 是可寫副本,但「進稽核後 round SSP 可否編輯」待 user 拍板。
  • AO 任務指派 UI(FE Wave 3):prep job 目前全未指派;指派 + task_assignees 顯示待做。
  • stg/poc migration:本弧 BE 改動多為 code(無新 migration);之前 4 支 round-flow migration 仍待 stg/poc 套。

§7 行為規範提醒

  • 不切 branchpush 等 user 明示;跨 repo 各自 commit、顯式 git add 禁 -am
  • 改 BE service/DI → 提醒 user 重啟(無 hot reload);服務 user 自己起
  • 動 FE 前讀 FE CLAUDE.md;不晶晶體;plan 假設先 verify 才開工
  • 收尾類動作(changelog / SUMMARY / design §11 / Notion)等 user 明確下令

§8 給 fresh session 的超短 prompt

讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-17-round-flow-fe-migration-handoff.md,
先讀「🧭 原始需求 WHY」+ §0 自檢 4 問答得出再開工。本弧:專案生命週期 FE 逐頁從舊 AP 引擎
遷到 v2 audit_round + living/round SSP(總覽已遷好,§3 參考實作)。下一棒:專案規劃頁
ProjectPlanningView 資料沒帶出來 → 比照總覽改吃 /ssp/<sspUid>/control-tree + roundUid 入口;
sspUid = round.ssp_uid ?? project.ssp.id 自動切換 living/round SSP(planning 拉主專案 SSP、
launch_audit 後拉 round frozen SSP)。跑 §5 pre-flight。不切 branch、push 等 user。