🟢 START HERE — FR-038 Q1:準備期 per-AO My Jobs(設計定稿 + 地基已驗,接手實作 5 增量)

給下個 session 的 prompt:「讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-Q1-prep-jobs-START-HERE-handoff.md,先過 §0 讀序硬 gate 懂 Q1 要解決什麼(requirement §4.3b + spec q1-prep-workflow-job-design.md),能答冷接自檢 4 問再碰 code。跑 §6 pre-flight + §7 verify,接手 Q1 實作 5 增量。」 本檔自包含。狀態為 2026-06-15 verified(現場跑真 CMMC L1 匯入 + B5 矩陣 + pytest)。

🔖 交接現況(2026-06-15 換 session 當下)

項目
進度 Wave 2B:B1✅ B2✅ B3a✅ B4.1✅ B4.2✅ B5(.1~.6)✅ OSCAL匯出✅ B5 AO收斂fix✅Q1 設計定稿 + 地基驗證完成(spec committed)。下一棒 = Q1 實作(5 增量)
主專案 branch / HEAD feature/oscal-refactor / d8b8d47f(本 session 10 commits)
主專案 working tree 乾淨,只有 M pyproject.toml(jedi-oscal-v2 dev path-dep,照規範勿 commit)
⚠️ **v2 套件未 commit dev 改動(B1 留下,本 session 沒動套件) repo ~/Projects/Jedicogy/module/jedi-python-package(branch feature/oscal-refactor):M catalog_service.pyM framework_service.py?? tests/catalog/test_catalog_service_import.py。dev path-dep,BE 重啟即生效;發版等整 feature 完成 + user 明示。別搞丟。**
跑得起來嗎 create_app() BOOT OK;python -m pytest test/ = 56 failed + 50 errors(持平 2A baseline、零新回歸)+ 9 skipped
push 未 push,等 user 明示
dev DB 新增真資料 本 session 匯入真 CMMC L1:framework id 169(code CMMC_L1_VFY_*)/ catalog 729(17 控制 / 59 assessment-objective / 273 method)。Q1 smoke 直接用 catalog 729 當資源庫底材。

🧭 開工前必懂:Q1 要解決什麼 + 本棒在大圖位置(先懂才准碰 code)

一句話:FR-038 把舊「AP 兼當稽核輪次」重寫成正式 OSCAL 模型。Q1 = 受評公司在「稽核前」的準備工作(My Jobs)——準備 SSP / 收證據(PM 教材:合規工作八成在準備期、證據在 SSP 階段就備好)。這塊在 2A 翻地基時被卡住(見下),是 Wave 2B 唯一還沒做的 BE 功能。

Q1 目標模型(requirement §4.3b 定案)

  • 「任務執行 / My Jobs」= 受評公司準備 SSP / 收證據的工作,與 AP / 稽核輪次無關(稽核員 Phase 3 走 AR 判定,不走 job 引擎)。
  • job 綁在專案 SSP 控制項的層級 —— 經 user 2026-06-15 拍板細化為:綁在 per-AO(assessment-objective)層(不是控制層)。每個 AO 一個準備 job,專案成立時就建(clone 資源庫 workflow 設定)。
  • workflow 引擎(jedi-flow-engine)保留,綁定點從舊「AP task」改為「專案 catalog 的 assessment-objective part」。

Q1 為何之前被卡(2A 留下的洞):舊「control→workflow template」設定(profile_assessment_workflow_mapping)依賴 v1 entity,v2 切換後 dark;新資源庫 module_frames.template_uid 全 NULL;vw_user_job_queue 的 control_* 留 NULL 佔位等 2B 接。本 session 把架構決策 + 資料地基都敲定/驗證完了,下一棒純實作。

冷接自檢 4 問(答不出回 §0 讀序):① Q1 的 job 綁在哪個資料實體上、為什麼是 AO 不是控制層?② 「AO」在新模型 = 哪張表的哪種 row、part_id 長怎樣?③ 為什麼設定用「單一預設 template / 資源庫」(A1)而非 per-AO 設定?④ 為什麼要「重建workflow_execution_control_mapping 而不是「加欄」?


§0 接手讀序(按序,1~2 是硬 gate)

  1. 本檔「🧭」節 + 通讀本檔
  2. 🔒 gate:q1-prep-workflow-job-design.mdQ1 spec,全讀 — 決策 D-Q1-1~7、資料模型、流程、view、scope)
  3. 🔒 gate:requirement-analysis.md §4.3b(Q1 定案)+ §6「任務執行」列 + design.md §4.4(主專案↔︎套件邊界:workflow/job 綁 SSP 控制項留主專案)
  4. 本 session 的 B5 AO 推導當範本:app/grc/service/assessment_result_app_service.py_derive_ao_map / _project_catalog_controls(Q1 要用同一套 AO 來源,建議抽共用 helper)
  5. flow-engine 既有 instantiate 邏輯參考:app/flow_engine/service/workflow_execution_service.py(不直接用、走 direct insert,見 §3)

§1 本 session 做了什麼(2026-06-15,全 verified)

B4.2 — AP 編輯(commit 6886ccec

AP 三件事接 v2 set_reviewed_controls / set_assessment_subjects / set_tasks + 重生草稿;auditor 守門(ap_uid → round → project 解析)。tasks 的 methods 落 props、timing 原樣 JSONB。

B5(.1~.6)— 完整稽核生命週期(commits 987948cd fb8ea7a8 49d94704 c138de79 f9e61129 cac9b844

  • B5.1 start-auditing 建 engagement AR + ar_result + AO 全量矩陣(AO 推導:AP→凍結SSP→profile→專案 catalog→各控制 assessment-objective part;無 AO 走控制層 fallback)
  • B5.2 逐 AO 判定(met/not_met/pending)+ observation(證據引用不複製)
  • B5.3 系統風險總結(多對多 finding 關聯,severity 存 characterizations)
  • B5.4 AR finalize(無 not_met→closed / 有 not_met→remediation + 自動生 POA&M)+ migration poam_id(已套 dev)
  • B5.5 POA&M item 列表/詳情 + 整改計畫 + 里程碑(三層 D-4b,assignee enrich + 180 天告警)
  • B5.6 close-round + launch-reverify(覆核連動關母輪)
  • 全 B5 子端點 nested under round_uid(非 contract 扁平 /ar-finding/{uid},避免改套件 — v2 query entity 無 uuid 欄)

OSCAL 匯出(commit f28f3d9c

GET /oscal/export/<doc_type>/<uid>(catalog/ssp/assessment-results/poam)回裸 OSCAL JSON 不包 envelope。權限暫 jwt-only(per-project authz 為 follow-up)。

B5 AO 收斂 fix(commit a971e81b)— 本 session 後段真資料驗出的真 bug

B5.1 用套件 list_aos,但 list_aosAO_PART_NAMES(assessment-objective + assessment-method + assessment-objects + objective)全集 → 矩陣把「查核方法」也誤算一筆判定。實測真 L1:59 objective vs 273 method → 矩陣會建 332 筆而非 59(超量 5.6x)。修成 get_by_control + filter name=='assessment-objective'(不動套件)。Q1 必須用同一個 AO 定義。

Q1 設計 + 地基驗證(spec commit d8b8d47f

見 §3。


§2 ⚠️ 會讓你做錯的陷阱 / 已定決策(先看)

  1. AO = 只算 assessment-objective part(user 拍板)。list_aos 不能用(含 method/objects);用 CatalogControlPartRepoImpl.get_by_control(cc.id) + filter name=='assessment-objective'B5 與 Q1 共用此定義(建議把 B5 的 _derive_ao_map 抽成共用 helper 給 Q1 用,避免兩邊 AO 兜不起來)。
  2. AO 是專案自有資料:專案 catalog 是 B2 clone(邊界②),其 catalog_control_parts 是 clone 來的、專案專屬,可掛 job。已驗 clone 有保留 AO parts(catalog 729 建專案後矩陣正確展 59 AO)。
  3. part_id 含控制前綴AC.L1-3.1.1_obj.2)→ 跨控制不撞名,可安全當 ao_part_id 識別。
  4. ⚠️ 綁定表不存在 / FK 壞本棒最關鍵的地基發現):compliance.workflow_execution_control_mapping DB 沒這張表(只有 ORM model),且 model 的 version_id FK 指向已被 drop 的 oscal.oscal_framework_versions(v2 改名 framework_versions)。→ 要「建」這張表(shape 改 workflow_execution_id + control_id + ao_part_id、丟掉壞 version FK),不是「加欄」。對齊 model/entity/mapper/repo(infra/flow_engine/models/workflow_execution_control_mapping.py 等)。
  5. task_assignees 不能 unassigned:PK = (project_id, control_id, task_id, user_id)。指派是 FE Wave 3,不在本棒 scope。本棒只建 job + 綁 AO,view rebind 讓「指派後」能顯示 control+AO。
  6. 同步建、direct insert(user 拍板 sync):per-AO job 在 project_start @transaction 內當場建。避免 320× BPMN parse —— BPMN 只 parse 一次拿 user-task id(或用 seed BPMN 的固定 task id),per-AO 走 WorkflowExecutionDomainService.create_workflow_execution + JobExecutionDomainService.create_job_execution direct insert。
  7. pytest 用 python -m pytest test/;boot 要補 dummy GITLAB/GITHUB env(見 §6)。
  8. plan 假設先 verify(v2 簽章 / 欄位 / 表存在性)才開工。不晶晶體。

§3 Q1 設計定稿(locked)+ 地基驗證結果

決策(全 locked,細節見 spec)

# 決策
D-Q1-1 綁定粒度 per-AO(每 assessment-objective part 一個準備 job)
D-Q1-2 AO 定義 只算 assessment-objective(與 B5 共用)
D-Q1-3 設定粒度 A1:單一預設 template / 資源庫(用 module_frames.template_uid
D-Q1-4 template seed 一個最簡「收證據」BPMN(單一 user task)
D-Q1-5 生成時機 同步(project_start @transaction 內,direct insert)
D-Q1-6 執行綁定 workflow_execution_control_mapping(workflow_execution_id + control_id + ao_part_id;丟壞 version FK)
D-Q1-7 指派 task_assignees(建立時不指派,PM 之後;FE Wave 3)

地基驗證(真 CMMC L1,2026-06-15)

  • 真匯入經正規 BE pipeline(framework_app_service.import_framework_versionimport_catalog_from_pdfframework_code="CMMC_2",L1/L2 共用同一 parser)→ catalog 729:17 控制 / 59 assessment-objective / 273 method / 17 statement。part_id AC.L1-3.1.1_obj.2
  • 以 catalog 729 建資源庫→專案→輪次→start-auditing:AO 矩陣 total=59(B5 fix 後;fix 前 332)。證明 per-AO pipeline 通 + clone 保留 AO parts

flow-engine 關鍵事實(實作要用)

  • master templatecompliance.workflow_templates(欄:uid/name/type/version/provider/xml=BPMN/enable)。最簡 BPMN = 單一 <bpmn:userTask id="collect_evidence" name="收證據">(start→userTask→end)。
  • WorkflowExecution model(jedi-flow-engine)必填:template_id(NN)、namestatus(WorkflowStatus)、type(WorkflowType);main_workflow_execution_id nullable。建:WorkflowExecutionDomainService.create_workflow_execution(entity)
  • JobExecution model 必填:workflow_execution_id(NN)、main_workflow_execution_id(NN)、template_id(NN)、template_job_id(NN=BPMN task id)、status(JobStatus)、type(JobType);name/description 可放 AO label。建:JobExecutionDomainService.create_job_execution(entity)(direct insert,不 parse BPMN)。
  • containerdi_containers/flow_engine/workflow_excution_containers.py 暴露 workflow_execution_domain_service / job_execution_domain_service / workflow_execution_control_mapping_domain_service / workflow_template_repo
  • My Jobs viewscripts/sql/view/vw_user_job_queue.sql — control_* 全 NULL 佔位;rebind = job(b) → workflow_execution_control_mapping(by b.workflow_execution_id) → control_id + ao_part_id → 專案 catalog catalog_controls / catalog_control_parts

§4 開工順位(5 增量,每增量 smoke + 顯式 git add commit)

每增量照 canonical:改 code → BOOT OK → 真資料 smoke(用 catalog 729)→ pytest 不多紅 → 顯式 git add commit。

  1. Q1.1 — seed template + 建綁定表
    • migration A:CREATE compliance.workflow_execution_control_mappingworkflow_execution_id bigint, control_id varchar, ao_part_id varchar,PK 視需要;GRANT cm_app;INSERT schema_migrations)。對齊 model/entity/mapper/repo(去 version FK、加 ao_part_id)。
    • migration B(seed):INSERT 一筆 master「收證據」workflow_templates(固定 uid + 最簡 BPMN,task id collect_evidence)+ UPDATE compliance.module_frames SET template_uid=<該 uid> WHERE "group"='resource-library' AND template_uid IS NULL
  2. Q1.2 — AO 推導共用 helper:把 B5 _derive_ao_map / _project_catalog_controls 抽成共用(如 app/grc/service/_ao_derivation.py 或 util),B5 + Q1 共用「assessment-objective only」來源。
  3. Q1.3 — project_start per-AO job 生成project_start_app_service.start_project_init_ssp_control_implementations 後,解析資源庫 template_uid(無→log skip 不擋成立)→ 對每個 in-scope 控制的每個 assessment-objective part:direct insert workflow_execution + job_execution + workflow_execution_control_mapping(ao_part_id, control_id)。注入 flow-engine domain services + workflow_template_repo + part repo。
  4. Q1.4 — vw_user_job_queue rebind:control_* 經 mapping → 專案 catalog control + AO(part_id)。migration(view 可移植)+ 套 dev。
  5. Q1.5 — 真資料 smoke + commit:用 catalog 729 建資源庫→專案→斷言每 assessment-objective part 各一 job + mapping(ao_part_id)+ vw_user_job_queue 能查到該 project 的 job(含 control+AO)。量測專案成立耗時(59 AO;L2 ~320 的話留意,spec §5 量考量)。

§5 該讀 / 預期改動的檔案

檔案 為何
app/project/service/project_start_app_service.py Q1.3 加 per-AO job 生成(_init_ssp_control_implementations line ~159 / start_project line ~203)
app/grc/service/assessment_result_app_service.py _derive_ao_map 抽共用 helper(Q1.2)
infra/flow_engine/models/workflow_execution_control_mapping.py + entity/mapper/repo(domain/flow_engine/... infra/flow_engine/... Q1.1 重建表 shape(去 version FK、加 ao_part_id)
di_containers/flow_engine/workflow_excution_containers.py / di_containers/project/project_containers.py wire flow-engine domain services 進 project_start
scripts/sql/view/vw_user_job_queue.sql Q1.4 rebind control_*
~/Projects/Jedicogy/.../jedi-flow-engine/.../models/{workflow_execution,job_execution}.py 必填欄位參考(direct insert)

§6 Pre-flight(必跑,可複製貼)

cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                      # feature/oscal-refactor
git status --short                             # 僅 ' M pyproject.toml'
git log --oneline -3                            # d8b8d47f / a971e81b / f28f3d9c
( cd ~/Projects/Jedicogy/module/jedi-python-package && git status -sb | head -6 )  # 套件 dev 改動還在
poetry run python -c "import jedi_oscal_v2; print('v2 OK')"
set -a; source .env; set +a 2>/dev/null
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
poetry run python -c "import eventlet; eventlet.monkey_patch(all=False, socket=True); import sys; sys.setrecursionlimit(5000); from core.app_factory import create_app; create_app(); print('BOOT OK')"
poetry run python -m pytest test/ -q -p no:cacheprovider --continue-on-collection-errors 2>&1 | tail -1  # 56 failed,...,50 errors(持平)

.env 在 shell source 會在 JSON 行報 parse error;跑 python script 改用 from dotenv import load_dotenv; load_dotenv()


§7 Verify B4.2/B5 + Q1 地基(確認現況,可複製貼)

cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
PW=$(poetry run python -c "from dotenv import load_dotenv; load_dotenv(); import os,json; print(json.loads(os.environ['DB_SECRET'])['rds_master_password'])" 2>/dev/null)
# 真 L1 catalog 729 的 AO 結構(Q1 smoke 底材)
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "
SELECT p.name, count(*) FROM oscal.catalog_control_parts p
JOIN oscal.catalog_controls cc ON p.catalog_control_id=cc.id
WHERE cc.catalog_id=729 GROUP BY p.name ORDER BY 2 DESC;"   # assessment-method 273 / assessment-objective 59 / statement 17
# 綁定表不存在(Q1.1 要建)→ 應回空 / NULL
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "SELECT to_regclass('compliance.workflow_execution_control_mapping');"  # 空 = 表不存在,要 CREATE
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "SELECT to_regclass('oscal.framework_versions'), to_regclass('oscal.oscal_framework_versions');"  # 前者存在 / 後者 NULL(壞 FK 別接)

B4.2/B5 端到端 smoke(可選複跑):見本 session B5 各 commit message 的 verify 段;canonical 流程 = 建合成 catalog→資源庫→專案→輪次→launch-audit→start-auditing→judge→risk→finalize→poam→close→reverify。


§8 行為規範重要提醒(適用本棒)

  • 不切 branch(兩 repo 都在 feature/oscal-refactor;branch 不對停下問 user)。
  • 可自行階段性 commit(顯式 git add 檔名、-am;各 repo 分開);push / 收尾 / 套件發版等 user 明示
  • pyproject.toml path-dep 勿 commit;v2 套件 dev 改動別搞丟、別發版。
  • 改 BE 後提醒 user 重啟 BE(無 hot reload);服務 user 自己起。
  • 跨 schema FK 字串帶 schema 前綴(記憶 feedback_cross_schema_fk_must_qualify)。
  • SQL migration:cmmgr + --single-transaction -v ON_ERROR_STOP=1,新表 GRANT cm_app + sequence、收尾 INSERT schema_migrations;本期 migration 只套 dev(stg/poc 待辦)。
  • 遇到架構/資料模型取捨、需選方案、scope 邊界 → 停下問 user(本 arc 已有多次:living-triple / SSP 控制項來源 / Q1 粒度與綁定 / AO 定義 / 同步vs背景)。
  • 量考量:per-AO L2 ~320,同步建走 direct insert、量測專案成立耗時;過慢再議背景(spec §5,本期 YAGNI)。

§9 收尾流程(Q1 做完 + user 明示才做)

盤點 commits → changelog(type=feat)→ analysis(Q1 per-AO 綁定 + 重建綁定表 + 同步建 取捨)→ SUMMARY → 回頭更新本 handoff ✓ + spec §狀態 + design §4.4 + api-contract(My Jobs/§3.2 job_count)→ memory feedback(≥1 條:如「list_aos 含 method 會超量、AO 只算 assessment-objective」「綁定表 DB 不存在要先驗 to_regclass」)→ Notion。push 等 user。

本 session 自身的收尾(B4.2/B5/匯出/AO fix 的 changelog / SUMMARY / analysis / memory / Notion)也都還沒做 —— user 還沒下收尾命令,全留著。下一棒若 user 要收 Wave 2B 全弧,一起補。


§10 不在本期 scope(別順手做)

  • FE Wave 3(資源庫 workflow 設定畫面、My Jobs 指派 UI、AP/AR/POA&M 畫面、輪次切換)。
  • job 自動指派:task_assignees 由 PM 手動(FE);本棒只建 job + 綁 AO。
  • 背景建:同步建為主,過慢才議(follow-up)。
  • per-control / per-AO 差異化 template(A2/A3,未來 override)。
  • 套件發 Nexuspyproject.toml 改回 pin(整 feature 完成 + user 明示)。
  • 正式環境 schema 遷移(stg/poc/prod;本期所有 migration 只套 dev)。
  • ISO/NIST(本期 CMMC-only);import-ssp 套件實作(defer)。
  • B4.2/B5/匯出/AO-fix 的收尾文件(等 user 下收尾命令)。

§11 本 session commits(branch feature/oscal-refactor,未 push)

d8b8d47f docs(FR-038): Q1 準備期 per-AO My Jobs 設計定稿(spec)
a971e81b fix(FR-038): Wave 2B B5 — AO 矩陣只取 assessment-objective(修 method 超量 5.6x)
f28f3d9c feat(FR-038): Wave 2B B5 — OSCAL 匯出端點(catalog/ssp/assessment-results/poam)
cac9b844 feat(FR-038): Wave 2B B5.6 — close-round + launch-reverify(覆核連動,完整生命週期收口)
f9e61129 feat(FR-038): Wave 2B B5.5 — POA&M item 列表/詳情 + 整改計畫 + 里程碑(三層 D-4b)
c138de79 feat(FR-038): Wave 2B B5.4 — AR finalize(定版)+ 有 not_met 自動生 POA&M
49d94704 feat(FR-038): Wave 2B B5.3 — 系統風險總結(多對多 finding 關聯)
fb8ea7a8 feat(FR-038): Wave 2B B5.2 — AO 逐項判定 + observation(證據引用不複製)
987948cd feat(FR-038): Wave 2B B5.1 — start-auditing 建 engagement AR + ar_result + AO 全量矩陣
6886ccec feat(FR-038): Wave 2B B4.2 — AP 編輯端點接 v2 set_*
  • migration 已套 dev(stg/poc 待辦):2026-06-15-fr038-audit-rounds-poam-id.sql(B5.4)。前置(前 session):2026-06-15-fr038-project-extensions-living-ssp.sql / 2026-06-15-fr038-audit-rounds-uid.sql
  • dev DB 另有真 L1 import(framework 169 / catalog 729)+ 多筆 smoke 合成資料(無害)。

§12 給 fresh session 的超短 prompt

讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-Q1-prep-jobs-START-HERE-handoff.md。
先過「🧭 開工前必懂」+ §0 讀序硬 gate(q1-prep-workflow-job-design.md 全讀 + requirement §4.3b + design §4.4),
能答冷接自檢 4 問(job 綁哪/為何 AO 層 / AO=哪張表 / 為何 A1 / 為何重建綁定表)才往下。
跑 §6 pre-flight(BOOT OK + pytest 56f/50e 持平)+ §7 verify(catalog 729 AO 結構 + 綁定表不存在)。
接手 Q1 實作 5 增量:Q1.1 seed template+建綁定表 → Q1.2 AO 推導共用 helper → Q1.3 project_start
per-AO job direct insert → Q1.4 vw_user_job_queue rebind → Q1.5 真資料 smoke(catalog 729/59 AO)+commit。
每增量 smoke + 顯式 git add commit。push/收尾/套件發版等 user 明示;不切 branch;pyproject.toml dev path-dep 勿 commit。

冷接可行性自檢 ✅

看本檔 + §0 讀序 + 跑 §6/§7 → 能確認現況(B4.2/B5/匯出/AO-fix shipped + commits + 套件 dev 改動 + 真 L1 catalog 729)、懂 Q1 WHY(準備期 per-AO My Jobs、為何 AO 層、為何卡)、知道 5 增量怎麼接、知道地基發現(綁定表 DB 不存在要建、壞 version FK 別接)、知道規範界線。不需 user 額外解釋即可開工。