Bug B + Parties Stepper UX Handoff — 2026-05-25

項目 內容
緣由 Phase 4 完工 + E option ship 後,user 發現 docx import 還是少 parties 資料(「責任單位 / 責任人員」tab 空白);我(前一 session)在 Phase 4 一直把 Bug B 標為「caveat」叫 user 手動 accept,迴避修正。User 點出問題後,因 session 注意力分散,handoff 給下個 fresh session。
Branch feature/ssp-oscal-alignment(兩 repo 一致)
接手前必讀 本文件 → 2026-05-25-phase4-COMPLETE-SUMMARY.md(Phase 4 完工狀態)→ 2026-05-25-phase1-phase2-FINAL-SUMMARY.md §8 L2(Bug B 原始記錄)
預估時間 1-2 小時(含 read FE flow + fix + verify + commit)
不在本 handoff scope L7 以外的 follow-ups(L8 進版 / L9 版號對齊)— 看 phase4-COMPLETE-SUMMARY

1. Bug B 完整 diagnosis

1.1 症狀

User 在 MF /module-frame/<uid>/template-edit 進 docx import (亞航-CMMC-SSP-20260520-1會議討論版.docx):

  • 預覽 stepper 顯示 5 個 parties
  • User 點確認 confirm
  • 後端 DB 內 oscal.parties + oscal.responsible_parties 對應 SSP shell 261 是空的
  • MF /template-edit 「責任單位」「責任人員」tab 空白

1.2 BE log 證據(已 confirmed)

[2026-05-24 23:46:34,183] [ssp-docx-import] _load_current_parties returned EMPTY for module_frame=6492b07a-2039-49e9-86b5-a9334299cfd5
[2026-05-24 23:46:36,539] [ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
    'new-1628102bb960bfc8': 'skip',
    'new-66dcffaee4b6081f': 'skip',
    'new-689443e94eb4d6c9': 'skip',
    'new-925c9cffb607ba86': 'skip',
    'new-9500c754dd80a637': 'skip'
}
[2026-05-24 23:46:36,549] [ssp-confirm] annotated party uid=new-1628102bb960bfc8 diff_status=added decision=skip
... (重複 5 次)
[2026-05-24 23:46:36,619] [ssp-confirm] no parties marked use_docx — nothing to write
[2026-05-24 23:46:36,630] [ssp-confirm] skipping strategy.write_parties — parsed_parties=0 hasattr=True

重點觀察

  1. BE 端 docx parser 成功 extract 出 5 parties(FE diff stepper 顯示出來)
  2. 全部 5 parties 的 diff_status=added(全新,不是 conflict / unchanged)
  3. FE 送來的 confirm payload 每一筆都帶 decision='skip'
  4. BE 正確地 skip 寫入(per decision contract)
  5. 結果:0 parties 寫進 DB

結論:BE 行為正確。Root cause 在 FE — diff stepper 預設 decision=skip 而不是 accept

1.3 不確定點(要 fresh session 確認)

不確定點 推測 怎麼 verify
FE diff stepper 預設 decision 的初始值在哪設? useSspDocxDraft.js composable 或 sspDocxImportStore.js 內,可能在 initDefaultDecisions 之類 function grep decision.*skip|use_docx|initDecisions 在 FE src/composables/useSspDocxDraft.js + src/stores/sspDocxImportStore.js
added / conflict / unchanged 三種 diff_status 各該預設什麼? 推測:added 該預設 accept(新 party,無風險)/ conflict 預設 skip(user 確認)/ unchanged 預設 skip(不重寫) 看 FE diff stepper component 對三個 diff_status 是否分流
FE diff stepper UI 是否顯示「電話/職稱/地址」欄位? 推測:沒顯示,user 看不到完整 party info 所以不敢 accept grep SspDocxImportPage.vue 內 parties step 欄位列表(電話 / 職稱 / 地址 / address / telephone / job_title)
是否同時要修「parties FE preview 缺欄位」(Issue 1)? ,跟 Bug B 同一條 stepper 元件,一起改成本更低 改 stepper 時順手加欄位 render

2. 該 read 的檔案(接手 fresh session 開工順序)

2.1 BE side(已 verify 正確,read 是為了避免 regression)

為何 read
domain/oscal/parser/docx_section_extractors.py:255-321(party fields) 確認 BE 已 extract 5 fields (name/title/address/telephone/email);line 318 extract_party_tables()
app/oscal/service/ssp_docx_import_app_service.py:847+ 區域(_filter_parties_for_write 確認 decision filter logic — fix 後 BE 應正確接 decision='use_docx' 寫入

2.2 FE side(主修改區

Read 目的
src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue 主 docx import page,看 parties step 在哪 render + 哪個 stepper component
src/composables/useSspDocxDraft.js docx draft state composable — 推測 default decisions 初始化在這
src/stores/sspDocxImportStore.js docx import pinia store — initDefaultDecisions 或類似 method 預設 'skip'
src/service/SspDocxImportService.js service 看 confirm payload shape — verify decisions field
FE docx import diff stepper 對應 component(從 SspDocxImportPage 內部 import 推) parties step render — 加電話/職稱/地址欄位 + 改預設 decision

2.3 Reference(前期相關 commit)

  • Phase 1+2 Bug A fix commit ba0cdd7a(docx confirm source_type=module_frame 補 ensure_shell)— 跟 Bug B 不相關,但同檔案修過
  • Phase 3 Bug C fix commit 8e8d5624(v2-bundle confirm 套 content_overrides)— 同檔案修過

3. Fix plan(兩個 option)

Option A: 全部 diff_status 預設 accept(最簡單)

// 在 FE store / composable 內 initDefaultDecisions:
parties.forEach(p => {
    decisions[p.uid] = { action: 'use_docx' }  // 不管 diff_status 都預設 accept
})
  • Pro: 一行改完,user 不用每次手動 accept
  • Con: 既有 parties 若被 docx 改了會被覆蓋 — user 可能沒看 diff 就誤改

Option B: Per diff_status differentiate(推薦)

parties.forEach(p => {
    if (p.diff_status === 'added') {
        decisions[p.uid] = { action: 'use_docx' }   // 新 party,預設加入
    } else if (p.diff_status === 'conflict') {
        decisions[p.uid] = { action: 'skip' }       // 衝突要 user 確認
    } else {
        // 'unchanged' 或其他
        decisions[p.uid] = { action: 'skip' }       // 不動既有
    }
})
  • Pro: 對齊 user expectation — added 自動加入(user 還能取消),conflict 預設保守
  • Con: 多兩行 logic

推薦 Option B,跟 BE strategy.write_parties 的「added → write / conflict → skip unless explicit accept」哲學一致。

額外修 Issue 1(parties preview 缺欄位)

在 stepper component render parties 表時加:

<Column field="title" :header="t('lang.xxx.col_title')" />   <!-- 職稱 -->
<Column field="address" :header="t('lang.xxx.col_address')" />
<Column field="telephone" :header="t('lang.xxx.col_telephone')" />

BE 已 extract,FE 只是沒 render。i18n key 可放 lang.ssp_docx_import.col.*


4. Verify checklist(fix 完後)

4.1 BE log verify

重啟 BE → 重做 docx import → grep log:

tail -300 log/app.log | grep -E "ssp-confirm|_filter_parties|annotated party|strategy.write_parties"

預期看到(fix 後):

[ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
    'new-...': 'use_docx',   # ← 不再是 'skip'
    ...
}
[ssp-confirm] annotated party uid=new-... diff_status=added decision=use_docx
... (5 筆)
[ssp-confirm] calling <strategy>.write_parties with ... parties
[ssp-confirm] strategy.write_parties returned parties_written=5

4.2 DB verify

SET app.is_super_admin='t';
SELECT count(*) FROM oscal.parties WHERE created_at > '2026-05-25 開始時間';
-- 預期 ≥ 5

-- responsible_parties (party → SSP/MF context link)
SELECT count(*) FROM oscal.responsible_parties
  WHERE context_type='ssp' AND context_id=<新建 SSP shell id>;
-- 預期 ≥ 5

4.3 FE 行為 verify

  • 進 docx import preview → parties step 顯示 5 parties,每筆預設打勾 (accept 狀態)
  • 顯示「電話 / 職稱 / 地址」欄位內容(不再缺)
  • 直接點 confirm → BE log 顯示 decision='use_docx' → DB 寫入

4.4 Regression verify(重要)

  • 既有 parties match 到 docx parties 的場景(diff_status='conflict' 或 'unchanged')→ 應該還是預設 skip,不被誤改
    • 怎麼測:先 manual seed MF 已有 5 parties,再 docx import 帶 4 同 name + 1 新 → confirm → 4 既有不變 / 1 新加入

5. 不在本 handoff 但相關的 leftover

5.1 Issue 3 / Option E 已 ship(前 session)

  • 啟動專案 clone 已補 InventoryItem + M2M(commit 099a86bb
  • BE /module-frame/<mf_uid>/template-ssp endpoint ship(同 commit)
  • FE MF /template-edit 加 v3 section reuse Phase 4 L2 元件(commit 01a51f6

User 該重啟 BE 後 → 進 MF /template-edit 的新「元件清冊」tab 應看到之前 docx import 寫入的 4 components + 1 LA。

5.2 Bug B + Issue 1 fix 後該 update 的 docs

  • design.md §11 補 §11.22 entry 紀錄 Bug B fix(mirror §11.17 Bug A 寫法)
  • docs/features/FR-028-2605-ssp-oscal-alignment/handoff/ 加 Bug B 收尾 SUMMARY
  • changelog batch(per memory feedback_changelog — 收尾才寫)

5.3 L8 / L9 進版(user 拍板才做)

  • L8: jedi-oscal 進版 + Nexus push + 主專案 pin 還原
  • L9: BE/FE 版號 1:1 對齊 (v1.1.0 → v1.2.0?)

詳見 2026-05-25-phase4-COMPLETE-SUMMARY.md §5。


6. 給 fresh session 的精簡 prompt

你接手 SSP OSCAL Alignment Phase 4 後續 — fix Bug B (docx import 5 parties
全 decision=skip → 0 寫入) + Issue 1 (parties FE preview 缺電話/職稱/地址欄位)。
前一 session 修了 Phase 4 L5/L1a/L2/L3/L6 + E option (E1/E2/E3) 但一直迴避
Bug B,user 點出後 handoff 給你。

按以下順序讀文件:
1. docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-b-and-parties-stepper-handoff.md(本 handoff — 必讀)
2. docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-phase4-COMPLETE-SUMMARY.md(Phase 4 完工狀態)
3. domain/oscal/parser/docx_section_extractors.py:318+(party extractor — 確認 BE 已 extract 5 fields)
4. app/oscal/service/ssp_docx_import_app_service.py:847+(_filter_parties_for_write)
5. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue(主 page)
6. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/stores/sspDocxImportStore.js
7. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/composables/useSspDocxDraft.js

開工前 pre-flight:
- 兩 repo branch: feature/ssp-oscal-alignment
- BE working tree: M pyproject.toml only
- FE working tree: clean
- BE 必須先重啟(前 session ship 的 E1/E2 endpoint + clone bug fix)
- BE test smoke: poetry run pytest tests/test_ssp_excel_import_app_service.py — pass

開工順位(看 handoff §3):
1. Read FE store + diff stepper 找預設 decision 初始化
2. 改 Option B 邏輯 (per diff_status 預設 accept/skip)
3. stepper component 加電話/職稱/地址欄位 render
4. FE vite build smoke
5. user 端重新 docx import verify (log + DB)
6. design.md §11 補 §11.22 entry
7. commit (FE 改動 + design.md)