過夜自主收尾決策記錄 — SSP 差異更新匯入復原

狀態 2026-06-16(凌晨自主作業)。user 2026-06-15 深夜授權「持續做完、不能進行的用推薦模式並記錄、權限開放、明早驗收」。 本檔記錄所有自主作出的決策,供 user 明早驗收時對照。原則:安全優先、不逾越(不 push / 不發 Nexus / 不起服務 / 不寫真 DB 非 rollback / 不盲建 FE UI)。

§1

範圍邊界(自主作業的紅線)

動作 做不做 理由
端到端整合測試(rollback-isolated 真 DB) ✅ 做 補 per-phase mock 鄰居的縫隙;rollback 不污染資料
serializer drift 對齊(FE-informed) ✅ 做 先 grep FE 確認實際值再改,非盲改
e2e BDD 場景(測試專案) ✅ 寫,不執行 執行需起服務 → 留 user
收尾文件(changelog/analysis/SUMMARY/Notion/design 同步) ✅ 做 + commit user 要求「做完 + 記錄」;不 push
起 HTTP 服務跑 smoke 規範「服務 user 自起」+ user 明早親自驗收
對 ssp 1482 真寫入(非 rollback) 怕污染 demo 資料
push(兩 repo)/ 發 Nexus / pin 還原 規範「push/發版等 user 明示」
建 FE 專案 SSP 匯入 UI 頁 大、需 user 看設計;記錄為 follow-up
§2

決策記錄(依時間)

(隨作業逐條補入)

D1 — source_type project_ssp vs ssp:BE app service 入口正規化(真 bug,非 cosmetic)

發現:grep FE 後確認 SspDocxImportPage.vue:135 在「更新專案 SSP」時送 source_type='project_ssp',走通用 docx route /ssp-docx-imports/parseapi.js:224,FE 沒用 implementer 新建的 scoped docx route)。docx serializer 已允許 project_ssp,但 BE docx app service _VALID_SOURCE_TYPES={"module_frame","ssp"} 會把 project_ssp 當無效 → 422。→ 真實 FE 的 docx 專案 SSP 匯入是壞的(單元測試用 'ssp' 沒抓到)。excel 專案匯入走 scoped route(route 內強制 'ssp')不受影響。 FE 現況:excel 專案 = scoped route forced 'ssp';docx 專案 = 通用 route + FE 送 'project_ssp'。兩格式值不一致是既有狀態。 決策:BE 兩個 import app service 在入口(manager gate + dispatch 之前)把 project_sspssp 正規化成同一 canonical(視為「專案 SSP」)。理由:(1) 修好真實 FE docx 壞掉的路徑;(2) 不改 FE(FE 已定 project_ssp);(3) 不動 excel scoped route 的 'ssp';(4) gate 在正規化後一樣對 project_ssp 生效,無繞過。被排除:改 FE 送 ssp(FE 已出貨 project_ssp、docx serializer 也是);全面 canonical 成 project_ssp(要改 excel scoped route + 更多面,風險大)。 回退條件:若日後決定全系統統一成單一值,可把正規化收斂掉。 狀態:✅ 完成,commit acc72361。正規化在 docx app service :120(gate :137 前)+ excel + 兩邊 confirm 防禦性正規化。16 tests(含非 manager + project_ssp → Forbidden)、BOOT OK、baseline 零回歸。gate 順序我親讀確認無繞過。

D2 — 端到端整合測試的 DB 隔離方式

發現:主專案 test/ 無 conftest;複用 v2 套件 jedi-oscal-v2/tests/conftest.py::db_session 的 rollback pattern,inline 在測試檔(開 SessionLocal()、綁 session_contextSET LOCAL app.is_super_admin='t' 繞 RLS、teardown rollback())。在低層(adapters/merge/import_ssp/snapshot 直接呼叫,繞過 @transaction 否則會 commit)測,DEV DB 不可達則整檔 skip。 隔離驗證:跑前後 oscal.system_security_plans count/maxid 完全相同(ssp=55 maxid=1487)→ 零持久化。 commit dcaa6e6dtest/test_ssp_import_pipeline_integration.py,4 tests)。

D3 — 🔴 整合測試抓到真實產品 bug:專案 SSP 更新時 by-component 敘述被丟棄

Bug:Model C 真實路徑(re-parse → merge → import_ssp(update) 進 living SSP)下,use_docx 控制項敘述覆寫 + 未匹配段落手動指派都不 persist。SoA props(IR 上的 status/applicability)會寫,但實作敘述文字(存在 by-component.description)被靜默丟。 根因parsed_docx_to_oscal_ssp/excel adapter 每次 parse mint 全新 this-system component uuid;by-component soft-ref 指它。import_ssp(update)(title,type) upsert component、保留既有 uuid,comp_map 只映既有 uuid(oscal_io_service.py:1118-1121)→ 新 by-component uuid 找不到 → skip(log: by-component skipped — component-uuid not found)。living SSP 既有 uuid 永不等於新 parse → 敘述永遠掉。per-phase 單元測試 mock 鄰居所以沒抓到;整合測試(真 import_ssp)抓到。 為何嚴重:這是功能主要用途(更新專案 SSP 敘述)。不修,明早 smoke 會「匯入回傳成功 counts 但敘述沒變」。 決策(Option B,套件層 import_ssp update path):upsert component 時把傳入 dict 的 component uuid(by-components 參照的那個)也映進 comp_map → 解析出的 component id;新舊 uuid 並存指同一 id。對既有 + 新 SSP 都成立(component 靠 title,type 配對與 uuid 無關)。 被排除:Option A(adapter 用 deterministic 穩定 uuid)—— 救不了既有隨機 uuid 的 living SSP(如 1482),只對全新建立的 SSP 有效。Option C(by-component 改用 (title,type) 對齊)—— 較侵入、改 schema 語意。 授權依據:user 2026-06-15 已點頭本弧套件改動 + 今晚「持續做完、推薦模式」。 狀態:✅ 完成。package commit bdfe780(comp_map 同時映傳入 uuid,oscal_io_service.py:1101 捕捉 incoming_uuid、:1135 setdefault(incoming_uuid, saved.id));main commit 93df50b4(整合測試 SEAM 翻正:敘述覆寫 + 手動指派現在 persist)。package 17 tests、整合 4 passed 隔離再驗、我親驗 BOOT OK(canonical env,subagent 的 boot-fail 是它漏帶 recursionlimit)、baseline 91/97 comm 零新回歸。聚焦 review Approved(setdefault first-wins 正確、create byte-identical)。 驗收重點:這是讓你明早 smoke「敘述真的有更新」的關鍵修復。

D4 — 收尾文件處理方式(誠實標註 smoke 待驗)

決策:撰寫 changelog / analysis / SUMMARY / design 同步 / plan 狀態並 commit(user 授權「持續做完」),但全部誠實標註「real-DB HTTP smoke 待 user 明早驗收」,不標 FIXED/verified。不 push(規範等 user)。不建 Notion「Done」任務(外部發佈 + 未經 smoke verify,逾越;列為 user 驗收後的收尾步驟)。FE scoped UI 不建(大、需 user 看設計;列 follow-up)。 被排除:標 FIXED / 建 Notion Done —— 未 smoke 前等於宣稱已驗,違反 CLAUDE.md「FIXED 沒 verify = 撒謊」。

D5 — e2e BDD(測試專案)+ FE scoped UI:刻意延後,不過夜盲做

決策:兩項都不在過夜做,列 follow-up 留有 user 在場的 session。

  • e2e BDD(compliance-manager-test):需起服務才能跑;且場景要對 FE 實際流程/selector,過夜寫了無法驗證、易與真實不符(反而誤導)。手測 checklist(SUMMARY §未驗證)已覆蓋同範圍,user 明早可直接照走。
  • FE 專案 SSP 匯入獨立 UI 頁:FE docx 已走既有通用頁(送 project_ssp,D1 修好後可通);excel 有 scoped service。是否要獨立入口是 UX 設計題,需 user 看設計,不過夜盲建(memory:FE 設計需謹慎)。 推薦模式:明早 smoke 通過後,若要 e2e,有 user 起服務、一起對 FE 流程寫+跑。

§3

過夜總結(給 user 晨間驗收)

  • 7 phase 全綠 + 兩段式 review;過夜自主抓到並修兩個真 bug(D1 docx 契約 422、D3 核心 by-component 敘述被丟)—— 都是明早 smoke 會踩的。
  • 端到端整合測試(rollback、零污染)覆蓋 upsert/keep_current/snapshot/manual 核心 seam。
  • BOOT OK(親驗)、baseline 91/97 零新回歸。
  • 收尾文件全寫+commit(changelog/analysis/SUMMARY/本記錄/plan/design),誠實標 smoke 待驗
  • 未做(等你):push / Nexus / FE 對齊 / Notion / e2e / FE UI —— 全列 follow-up。
  • pyproject.toml(dev path-dep)維持 working tree 未 commit。