# 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 ×5` → `no parties marked use_docx` → 0 parties to write。

**Root cause**：`stores/sspDocxImportStore.js:initDefaultDecisions` 對 added parties 留 `action=null` → `buildConfirmPayload._safeAction(null)='skip'` → 5 parties 全 skip。

**Fix（commit FE `f31d0cf`）**：新增 `_defaultPartyAction` helper，per diff_status seed default：
- `added` → `use_docx`（新 party 預設加入）
- `gone` → `keep_current`（既有 link 預設不動）
- `conflict / changed / unchanged` → `skip`（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 cause**：`module_frame_write_strategy._upsert_responsible_party:662-663`：

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

`write_parties:530` 傳 `role_id=parsed.role or ""`，`parsed.role` 來自 `_dict_to_parsed_parties:1104` 的 `p.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'`）

```sql
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」段。
