FR-040 行程鉤稽控制項/受評對象/參與人員 — 實作計畫

Date: 2026-06-20 · 設計見同夾 design.md · 決策見 docs/analysis/2026-06-20-ap-task-control-subject-linkage.md 原則:先文件後 code;開工前先跑「Phase 0 pre-flight」驗證假設(plan→build 常漂移)。

§1

Phase 0 — Pre-flight 驗證(開工第一步,逐項確認再動)

  1. jedi_oscal_v2 ApTasksEntity 確有 associated_activities / subjects / responsible_roles(JSONB)—— 已查屬實。
  2. ap_assessment_activities 表/entity 確有 related_controls / props / steps —— 已查屬實。
  3. 確認 ap_assessment_activities 的 repo + set/upsert 方法jedi_oscal_v2/infra/repository/ap/ap_assessment_activities_repo_impl.py 的 CRUD signature (能否 by ap_id 全量覆寫,mirror ap_tasks 的 set_tasks)。
  4. 確認 AssessmentPlanAppService.set_tasks / self._ap.set_tasks 的套件 signature (現在只寫 ap_tasks;要擴成同 transaction 內先寫 activities 再寫 tasks)。
  5. 確認 control-id 形狀:related_controls.control-selections[].include-controls[].control-id 用的是 OSCAL control-id(如 ac-6.1),跟 Tab1 reviewed-controls 存的 control_ids 同源(verify 一致,否則對不上)。
  6. 確認 Tab2 受評對象(ap_assessment_subjects)回前端的 subject 結構(subject_uuid / subject_type / title), 作為前端 options + payload 來源。
  7. 確認專案參與者查詢(auditor/manager party uuid / user uuid)既有 service(FE 受評對象/參與者面板已有類似)。
§3

Phase 2 — BE 寫入(AssessmentPlanAppService.set_tasks 擴充)

檔:app/grc/service/assessment_plan_app_service.py

  • set_tasks(ap_uid, tasks, ...) 每條行程:
    1. ApAssessmentActivitiesEntityrelated_controls = controls → control-selections 結構; props = methods → [{name:"method",value:m}](沿用既有 _methods_to_props);uuid = 新 uuid4。
    2. ApTasksEntitytiming(既有)、associated_activities = [{activity-uuid: <上面 activity uuid>}]
    3. link 表ap_task_subjects(subjects)、ap_task_participants(participants),by task_id。
    4. 全量覆寫:同 @transaction 內依序重建 activities → tasks → link 表。
  • 新增 helper:_controls_to_related_controls / 反向(controls ↔︎ related_controls JSONB)。
  • methods 仍走 props(不動既有 _methods_to_props),只是搬到 activity 而非 task。
  • subjects/participants 不再組 OSCAL JSONB(直接落表,OSCAL 形狀交給 Phase 1 mapper)。
§4

Phase 3 — BE 讀取(AP detail 還原)

同檔 _to_ap_detail(約 line 127-144)

  • tasks 改成 join ap_tasks + ap_assessment_activities + ap_task_subjects + ap_task_participants
  • 每條還原:methods(activity.props)、controls(activity.related_controls)、 subjects(ap_task_subjects)、participants(ap_task_participants)、timing
  • audit 欄位規範:participants/subjects 的 party/subject uuid → enrich nickname/name(比照既有 enrich pattern)。
§5

Phase 4 — FE(RoundApAuthoringView.vue Tab3)

  • 載入:除既有 fetchAp,補抓 Tab1 控制項集、Tab2 受評對象集、專案參與者集(多數已在頁面其他 tab 抓過,能重用就重用)。
  • 每條行程 row 加 3 個 MultiSelect:控制項 / 受評對象(依 type 分組)/ 參與人員。
    • options 分別 = Tab1 勾選集 / Tab2 全集 / 參與者(auditor·manager)。
  • saveTasks payload 擴充 controls / subjects / participants(見 design §6)。
  • readonly 規則沿用既有。
  • i18n:補欄位標籤 key(zh-tw + en),沿用 lang.ap_authoring.*
§6

Phase 5 — 測試

  • 套件 pytest(jedi_oscal_v2):新 model/repo CRUD + mapper round-trip(表 ↔︎ OSCAL task.subjects/responsible-roles)。
  • BE pytest:set_tasks round-trip(存→讀形狀一致)、methods 仍落 props、controls 落 activity、 subjects/participants 落 link 表;全量覆寫不殘留舊 activity/link。logger patch autouse fixture(DBLogHandler 雷)。
  • FE/E2E:在 compliance-manager-test 專案補(行程加控制項/對象/人員 → 存 → 重整還原)。
  • 測試計畫由 feature-test-planner agent 產出 test-plan.md(Phase 4 SOP)。
§7

DB / 套件異動

  • 新增 2 張表(jedi_oscal_v2):ap_task_subjects / ap_task_participants(見 Phase 1)。
  • migration scripts/sql/2026-06-2x-fr040-ap-task-link-tables.sql(建表+GRANT+index+schema_migrations),dev 先套。
  • 控制項/方法/timing 零 schema 變更(用現有欄位)。
  • 套件異動走 dev poetry path-dep,feature 完成 + smoke 過才發 Nexus(依套件發版規範)。
§8

Phase 6 — 稽核人員 = AP metadata.parties + 就地新增(design §10,B 方案,待 user approve)

確認結果:零新表 —— oscal.assessment_plans.metadata_id(1:1) 已存在、oscal.parties/oscal.rolesmetadata_id scope,重用即可。per-task 沿用既有 ap_task_participants

  • Phase 6.0 pre-flight:確認 (a) party_repo_impl / SSP party CRUD 能以任意 metadata_id 操作(非寫死 SSP); (b) AP draft 生成時 metadata row 是否已建、是否已有 roles;(c) 權限(誰能在 AP 加 party)。
  • BE:新增 ap_party_app_service(list/add/update/delete parties,scope = ap_uid → assessment_plans.metadata_id), mirror ssp_party_app_service;首次操作確保 oscal.rolesassessor(AP metadata scope)。 route:GET/POST /ap/{apUid}/partiesPUT/DELETE …/parties/{uid}。DI wiring。
  • set_tasks/_ap_detail:participants 的 party_uuid 改指 AP party uuid(資料形狀不變,來源語意換)。
  • FEtaskParticipantOptions 改抓 GET /ap/{apUid}/parties(取代 SSP people); 下拉加「+新增」inline quick-add(姓名 / 類型 / role=assessor)→ 呼 POST parties → 選入。
  • OSCAL 匯出(後續):AP parties→metadata.parties、assessor→metadata.roles、ap_task_participants→task.responsible-roles。
  • 零新表 / 零 migration(重用 oscal.parties/roles/metadata)。
  • 取代過渡版(commit 1e8c231 稽核人員=SSP parties)。
§9

DB / 套件異動

  • 新增 2 張表(jedi_oscal_v2):ap_task_subjects / ap_task_participants(見 Phase 1)。
  • migration scripts/sql/2026-06-21-fr040-ap-task-link-tables.sql(建表+GRANT+index+schema_migrations),dev 已套。
  • 控制項/方法/timing 零 schema 變更(用現有欄位)。
  • Phase 6(稽核人員 AP parties)零新表:重用 oscal.parties/oscal.roles(metadata-scoped)。
  • 套件異動走 dev poetry path-dep / symlink,feature 完成 + smoke 過才發 Nexus(依套件發版規範)。
§10

Phase 7 — 說明改「每方法查核指引」(activity.steps)(✅ 實作完成 2026-06-21,待手測驗收)

完整設計見 design.md §11。零套件改動、零 migrationap_assessment_activities.steps 欄位早已存在、 entity/mapper 已雙向接好)。BE 12 測試綠 / FE build 過。changelog docs/changelog/2026-06-21-feat-fr040-phase7-ap-task-step-guidance.md。 實作細節對齊下方;差異:i18n 區塊標題實際用 task_guidance / task_guidance_ph(非 task_step_guidance)。 附帶同頁修補:查核指引方法標籤改 soft pill;受評對象 party 角色改走 lang.oscal_role.ssp_party_role i18n。

  • BEassessment_plan_app_service.py):
    • set_tasks:payload steps:[{method, description}]activity.steps,每筆補 uuid/props=[{name:"method",value:method}]/title=method/description。規則 steps ⊆ methods。
    • _ap_detail:讀 activity.stepssteps:[{method(from props), description}]
    • helpers _steps_from_payload / _steps_to_payload
    • route _TaskItemsteps 欄(List of {method, description},load_default None)。
  • FERoundApAuthoringView.vue):
    • TaskRow 增 stepGuidance: Record<methodValue,string>;載入由 tk.steps 還原。
    • 卡身「查核方法」下加「查核指引」區:v-for over tk.methods 動態列每方法一個 Textarea。
    • saveTasks payload steps = methods.filter(有指引).map(m => ({method:m, description:guidance[m]}))
    • i18n:區塊標題 task_step_guidance 等。
  • 測試:set_tasks round-trip 含 steps;steps ⊆ methods;method 取消其 step 不存。
  • description(整體備註)保留不動。
§11

Phase 8 — 查核指引下沉 AO 層 + 行程逐列儲存(⏸ 評估後定案暫不做 2026-06-21)

定案維持 Phase 7 per-method(每方法一條 textarea)為最終形態,AO 下沉 + 逐列儲存暫不做。 理由與未來反悔條件見 design §12 banner。下方 8.0~8.5 保留為「若未來要 per-AO 報表自動拆分」時的現成 plan。

Phase 8.0 — Pre-flight(開工第一步,逐項驗,漂移就停)

  1. AO 顯示 label 來源catalog_control_parts 的 AO part,[a][b][c] 標籤從哪來 — 驗 part.props(name=label?) vs part_id 尾碼 vs prose 首段;FE grid 顯示要的 label/prose 取得方式定案。
  2. AO 端點落點 + DI:AP app service 現無 _part_repo/_catalog_control_repo/_profile_import_repo; 決定 (i) AP service 補 wiring(傾向,read 歸屬 AP)還是 (ii) route 借 assessment_result_app_service 既有 _project_catalog_controls+_derive_ao_map。驗 derive_ao_pairs 能否順帶回 part title/prose(目前只回 id)。
  3. 逐列儲存 repo 能力:驗 jedi_oscal_v2 ApTasksRepo/ApAssessmentActivitiesRepo 有無單列 update/delete by uid/id(現 set_tasks 走全量 delete-all+_ap.set_tasks 全覆寫);task 是否有穩定 uid_ap_detail 已回 t.uid → 有)。
  4. OSCAL step shape:確認 select-control-by-id.statement-ids 是放 AO _obj.N 的正確欄(vs control-objective-selections); 挑與 AR/匯出未來相容者。
  5. control_id ↔︎ catalog 比對鍵一致性(沿用 Phase 7:related-controls 的 control-id == reviewed-controls control_id == catalog control_id)。

Phase 8.1 — BE:AO 清單端點

  • assessment_plan_app_service.list_control_objectives(ap_uid){control_id: [{ao_id, label, prose}]},只取 AP reviewed-controls。
    • 重用 AR 既有 AO 推導鏈(cc_map ← AP→SSP→profile→catalog;derive_ao_pairs),補抓 part title/prose/label。
  • route GET /ap/{apUid}/control-objectives(auditor/manager read;沿用既有 round/AP 解析守門)。
  • DI wiring 依 8.0.2 決定。

Phase 8.2 — BE:steps 改 (control_id, ao_id, method) + helpers

  • _steps_from_payload([{control_id, ao_id, method, description}]) → OSCAL step(props method + reviewed-controls.control-selections.include-controls[{control-id, statement-ids:[ao_id]}],ao_id 為 None 則省 statement-ids)。只存有指引者。
  • _steps_to_payload(activity.steps)[{control_id, ao_id, method, description}](向後相容:無 reviewed-controls 的舊 step → control_id/ao_id=None,歸控制層)。
  • serializer _TaskStepItemcontrol_id(allow_none) / ao_id(allow_none)。

Phase 8.3 — BE:逐列儲存端點(Part B)

  • add_task(ap_uid, task) → 建 1 task + 1 activity + link,回 uid。
  • update_task(ap_uid, task_uid, task) → 更新該 task 的 activity(related_controls/props/steps) + 重建其 link 表,by task_uid。
  • delete_task(ap_uid, task_uid) → 刪 task(cascade activity + link)。
  • routes:POST /ap/{apUid}/tasksPUT/DELETE /ap/{apUid}/tasks/{taskUid};守門沿用。
  • 既有全量 set_tasks 保留(相容/批次);helper 抽共用(單列與全量共用組裝邏輯)。
  • repo 單列能力不足則於套件補(走 symlink/path-dep,發版等 user)。

Phase 8.4 — FE:控制項→AO→方法 指引 grid + 逐列儲存

  • 載入 GET …/control-objectivestk.stepGuidance key 改三元組 "<control_id>|<ao_id>|<method>"
  • 卡身指引區 grid:v-for 控制項 → v-for AO → v-for 已選方法;無 AO 控制項退單層。
  • 每卡「儲存此行程」呼單列端點、「刪除」呼 delete;新增呼 add。未存變更離開提示。
  • i18n:AO/grid 標題、儲存/刪除單列按鈕。

Phase 8.5 — 測試

  • BE:_steps_from_payload/_steps_to_payload 含 AO round-trip + 向後相容(舊 method-only step); list_control_objectives AO 推導(含控制層 fallback);add/update/delete_task 單列正確 + 不誤動他列。
  • FE/E2E(compliance-manager-test):行程加多控制項多 AO 指引 → 逐列存 → 重整還原。

DB / 套件異動(Phase 8)

  • 零新表 / 零 migration(沿用 ap_assessment_activities.steps)。
  • 若 repo 單列 add/update/delete 能力不足 → jedi_oscal_v2 補 repo 方法(dev symlink/path-dep,發版等 user 明示)。
§12

收尾

  • changelog(type=feat):套件加表+mapper / BE set_tasks 擴充 / FE 三連動 / Phase 6 AP parties / Phase 7 steps / Phase 8 AO 指引+逐列儲存,各別記。
  • design.md §9 範圍外項目逐一保留為 follow-up。