Bug B FIXED — 收尾 SUMMARY(2026-05-25)

項目 內容
Branch feature/ssp-oscal-alignment(兩 repo 一致)
起源 handoff handoff/2026-05-25-bug-b-and-parties-stepper-handoff.md
涵蓋 fix Bug B 兩段(§11.22 FE store + §11.24 BE adapter)+ Issue 1(§11.23 FE PartyDiffCard 欄位)
Verify evidence mf 368 (5eb137c8) responsible_parties 5 筆(1 org responsible-organization + 4 person system-user
接續 Bug D(leveraged services 寫 1/4)→ 開新 handoff 2026-05-25-bug-d-leveraged-services-handoff.md

1. Bug B 完整 root cause(兩段)

Bug B 從 2026-05-20 Phase 1+2 SUMMARY §8 L2 第一次推測,到本次 2026-05-25 收尾,累計兩段 root cause + 一段 cross-source漏修

1.1 第一段(§11.22)— FE store seed default

症狀:docx confirm 後 BE log decision=skip ×5no parties marked use_docx → 0 parties to write。

Root causestores/sspDocxImportStore.js:initDefaultDecisions 對 added parties 留 action=nullbuildConfirmPayload._safeAction(null)='skip' → 5 parties 全 skip。

Fix(commit FE f31d0cf:新增 _defaultPartyAction helper,per diff_status seed default:

  • addeduse_docx(新 party 預設加入)
  • gonekeep_current(既有 link 預設不動)
  • conflict / changed / unchangedskip(user 主動確認才覆寫)

連帶 §11.23 Issue 1:PartyDiffCard.fields 從 4 欄擴 7 欄(補 title / address / telephone)+ formatValue telephone alias。

1.2 第二段(§11.24)— BE adapter 漏補 default role

症狀:§11.22 fix ship 後 user retry,BE log decision=use_docx ×5 + parties_written=5(看起來成功),但 mf template-edit 仍 0 parties。DB 直查發現 oscal_parties 寫 5 筆但 oscal_responsible_parties link 一筆都沒建

Root causemodule_frame_write_strategy._upsert_responsible_party:662-663

if not role_id or not party_uid:
    return    # ← 空 role_id silent skip,無 raise / log

write_parties:530role_id=parsed.role or ""parsed.role 來自 _dict_to_parsed_parties:1104p.get("role")。docx party JSONB 由 _parse_party_table 抓,只 set name / title / address / telephone / email / party_type,沒 role → None → "" → silent skip。

Cross-source 漏修:Excel 端 2026-05-21 已修同一 bug(ssp_excel_import_app_service.py:1956-1968 註解清楚),但 docx 沒套。

Fix(commit BE fef5eecf:mirror Excel pattern,_dict_to_parsed_parties 加 default role:

  • party_type == "organization"'responsible-organization'
  • 其他 → 'system-user'

1.3 為何「parties_written=5」騙了兩段時間

write_parties 回傳的計數只算 oscal_parties 寫成功的 row,沒驗 oscal_responsible_parties link 建立。這是 silent-failure 計數,混淆 caller — 兩個 session 都被它誤導,把根因鎖在錯的地方。

下次寫類似 strategy method 時,計數 metric 必須 echo 最終 side-effect(兩張表都驗),不能 stop 在 silent-skip 之前。


2. Commits 清單

Repo Commit 內容
BE ba7885d4 docs: design §11.22 + §11.23 reconciliation(Bug B 第一段 + Issue 1)
BE fef5eecf fix: _dict_to_parsed_parties default role + design §11.24(Bug B 第二段)
BE 本 commit docs: 本 SUMMARY + Bug D handoff
FE f31d0cf fix: store _defaultPartyAction + PartyDiffCard.fields 擴展 + SspDocxImportPage 過時 comment

push 狀態:BE 3 commits + FE 1 commit 待 push(等 user 拍板)。


3. Verify Evidence

3.1 BE log(fix 後 retry,2026-05-25 01:11:56)

[ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
    'new-1628102bb960bfc8': 'use_docx', ...   # 5 筆全 use_docx
}
[ssp-confirm] _filter_parties_for_write resolved 5 parsed parties to write
[ssp-confirm] calling ModuleFrameWriteStrategy.write_parties with 5 parties
to source_type=module_frame source_uid=5eb137c8-84b6-4894-b0dd-a25ae092a8d2
[ssp-confirm] strategy.write_parties returned parties_written=5

3.2 DB(cmmgr SET app.is_super_admin='t'

SELECT id || ' role=' || role_id || ' party=' || party_uuid::text
  FROM oscal.oscal_responsible_parties
 WHERE context_type='module_frame' AND context_id=368;
-- 216 role=responsible-organization party=ecbe22dd-...
-- 217 role=system-user party=5a1c53b0-...
-- 218 role=system-user party=9c2c6408-...
-- 219 role=system-user party=f1d242db-...
-- 220 role=system-user party=9ef01730-...
-- (5 筆 — 1 org + 4 person)

3.3 FE 行為

mf /template-edit 「責任單位 (1) / 責任人員 (4)」tab 顯示完整 — user 視覺確認 OK。


4. 殘留 / 不在 scope

項目 處理
mf 367 (fc51dd08) 失敗 import 殘留 DB 寫了 5 個 oscal_parties (id 1266-1270) 但無 link。_upsert_party idempotent → 不會 duplicate。mf 367 留著無害(user 可手動刪)
Bug D — leveraged services 寫 1/4 開新 handoff 2026-05-25-bug-d-leveraged-services-handoff.md,下個 session 處理
jedi-oscal _upsert_responsible_party silent-skip pattern 列入 jedi-oscal 進版時的 cleanup — 改 log.warning 比 silent return 安全
Cross-source ParsedParty adapter 共用 helper docx / Excel / 未來 OSCAL JSON 各自 build ParsedParty,沒共用 → 下次又會漏。列入 future refactor
進版(L8 jedi-oscal Nexus + L9 BE/FE 版號對齊 v1.2.0) User 拍板才做

5. 教訓(Bug B 兩段合計)

  1. 「寫成功」的計數要驗到最底層的 link 表,不能停在中間層:parties_written=5 騙了兩段時間。Strategy method 寫多張表時,return 計數要 echo 最終 side-effect。
  2. Cross-source feature fix 要做 consistency audit:Excel 已修 default role bug 4 天後,docx 才被同個 user 踩到。未來 import 相關 fix 必檢查所有 source path。
  3. if not X: return 是危險 silent-skip pattern:caller 100% 沒辦法察覺。jedi-oscal 進版時改 raise 或至少 log.warning
  4. 直面 bug,不要 caveat 甩鍋:前 session 把 Bug B 標 caveat 叫 user 手動 accept 迴避 fix 是 anti-pattern — 給 user 重複勞動 friction。本 session 完整修 + DB 兩張表 verify 才算 done。
  5. handoff 推測要 verify 不要直接 commit message 寫死:Phase 1+2 SUMMARY §8 L2 推測「FE diff stepper UX 預設 skip」其實只是真實 root cause 第一段的部分描述 — 第二段(BE silent skip)完全沒覆蓋到。下次 handoff 要明確分「推測 / 已 verify」段。