本卡屬 FR-143(母卡見上一張),第 1 棒:jedi-oscal-v2 匯出修四處不合規、jedi-compliance-audit 守門從「全部結案」改「計畫齊全」、task 形狀改成對應系統任務。後面三棒都靠這棒的 task 形狀,先跑。
改善計畫的資料照 OSCAL 標準存在三層表(風險 → 處置計畫 → task)。現在匯出的 JSON 用官方 schema 驗會失敗:task 沒帶到期日和負責人、引用 finding 時塞了一個自創的鍵、檔案裡引用的 finding 和 risk 清單是空的、task 多了標準沒有的狀態欄。這棒把這四處修掉。另外流程現在要等「每個里程碑都完成」才能往下走,改成「每個任務都排好計畫」就放行。task 的意義也要改:一筆 task 對應一個系統任務(流程),存那個流程的 uid、執行人員清單、到期日、改善措施。
pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py:1538 _risk_to_json(mitigating-factors 留空即可)
pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py:1564 _remediation_to_json
pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py:1584 _milestone_to_task_json ← 沒帶 timing/responsible-roles
pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py:1600 _export_poam ← findings[]/risks[]/observations[] 空
pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py:1685 _poam_item_to_json ← related-findings 塞 finding-id
pkg/jedi-oscal-v2/jedi_oscal_v2/infra/model/poam/oscal_poam_milestone.py task 表:task_type/title/description/assignee_user_id/target_date/status/completed_at/props
pkg/jedi-compliance-audit/jedi_compliance_audit/app/service/audit_round_pass_transitions.py:96 _close_on_pass:all_items_closed 不成立拋 GRC_POAM_NOT_ALL_CLOSED
pkg/jedi-compliance-audit/jedi_compliance_audit/app/service/poam_app_service.py:421 all_items_closed;:434 earliest_milestone_dates;:449 milestones_by_control(493 行,可加但接近上限)
compliance-manager-be/common/code/flow_control_error_code.py:109 GRC_POAM_NOT_ALL_CLOSED 文案
oscal_io_service.py 超標,新開 pkg/jedi-oscal-v2/jedi_oscal_v2/app/service/io/poam_export_parts.py,把 _milestone_to_task_json、_poam_item_to_json、_export_poam 的組裝邏輯搬進去,原檔三處各留一行呼叫。type=action;timing={"on-date": {"date": target_date}};responsible-roles=[{"role-id": "remediation-owner", "party-uuids": [...]}],party-uuid 從 props.assignee_user_ids 反查 SSP metadata.parties(既有 matched_user_id 機制,查法照 _enrich_party_user_org_names 反向);查不到的人只寫 props[{name: assignee, value: 名字}],不寫 responsible-roles、不造假 uuid。status/completed_at 不匯出。finding-uuid(與 remarks),剝掉 finding-id。DB 的 related_findings JSONB 照存不動。_export_poam 組裝時把 poam-items 引用到的 finding、其 risk(含 remediations 與 tasks)、finding 引用的 observation 一併寫進 findings[]/risks[]/observations[],uuid 與 AR 那邊相同;只組 JSON,不複製 DB 列。props 固定放 {job_uid, assignee_user_ids: [...]},assignee_user_id 存第一人(向後相容);title=任務名、description=改善措施、target_date=到期日。remediation_service.add_milestone 加 job_uid、assignee_user_ids 參數寫進 props(先查既有簽名,用 kwargs 擴充不改既有呼叫者)。poam_app_service 加 all_items_planned(round_entity):每個 item 底下 lifecycle=planned 的 remediation 至少一筆 task,且每筆 task 的 target_date、props.assignee_user_ids(非空)、description 都有值。_close_on_pass 改呼叫它;GRC_POAM_NOT_ALL_CLOSED 文案改「尚有任務未完成規劃」(code 字串 GRC_412005 不變,FE i18n 由棒 3 同步)。earliest_milestone_dates/milestones_by_control 改成回傳逐 task 清單 [{job_uid, target_date, assignee_user_ids, advice}](給棒 5 退卡用),保留舊函式名但回傳形狀改;grep 呼叫者(目前只有 not_met_job_redo_service.py)一起改。先查再寫:party 反查用既有 matched_user_id 機制(app/oscal/service/ssp_docx_import_app_service.py:_enrich_party_user_org_names 是反向例子);props 讀寫已有 JSONB 欄;不要另開表。
_milestone_to_task_json 給一筆有 target_date+兩個 assignee 的 task → 輸出含 timing.on-date.date 與兩個 party-uuid;給一筆沒 target_date → 沒有 timing 鍵(不輸出 null)。_poam_item_to_json 輸出的 related-findings 每筆只有 finding-uuid(突變:把剝鍵那行拿掉,測試要紅)。all_items_planned:三個 task 其中一個缺 description → False;補上 → True。_export_poam 輸出用 ~/.claude/skills/oscal-knowledge/references/json-schemas/oscal_poam_schema.json 跑 jsonschema 驗證通過。既有測試在各套件 tests/,新測試放同目錄。