# Bug H FIXED — 收尾 SUMMARY（2026-05-25 晚）

| 項目 | 內容 |
|---|---|
| Branch | `feature/ssp-oscal-alignment`（兩 repo + 兩 jedi 套件 path dep）|
| 起源 handoff | `handoff/2026-05-25-bug-h-word-parties-labels-fields-handoff.md` |
| 涵蓋 fix | **Bug H**（4 子 bug FE label + fields）+ **Bug H-5**（BE 解析 email 漏掉）+ **Bug H-6**（system-wide auto-fill org_unit_id 假鉤稽）— user 手測一路追出來的 3 段連鎖 bug |
| Verify evidence | mf 372 (`2c4ba0b8-...`) template-edit + parse_job 153（fix 後 email_filled=4 vs fix 前 0）+ 髒資料清理 467 row 後鉤稽欄全「未鉤稽」 |
| 接續 | Bug A / B / C / D / H 全 close，design §11.17 + §11.22 + §11.23 + §11.24 + §11.25 + （待補）§11.26 + §11.27 + §11.28 完整覆蓋 ssp-oscal-alignment Phase 4 fix arc |

---

## 1. 三段 bug root cause

### Bug H — Word import 4 子 FE bug（純 FE）

| 子 bug | 症狀 | Fix |
|---|---|---|
| H-1 | org party label 散落「責任單位 / 單位 / 單位名稱」3 寫法 | 統一改「**負責單位**」（i18n + ROLE_LABEL map + DataTable column） |
| H-2 | org party preview card / DataTable / Dialog 三處缺電話 + 地址 | 三元件各加 InputText（i18n key 已存於 `field_phone` + `field_address`）|
| H-3 | person party label 散落「責任人員 / 人員」2 寫法 | 統一改「**參與人員**」 |
| H-4 | person party 同 H-2 缺電話 + 地址 | 三元件各加 InputText |

**Root cause**：preview (`PartiesSection.vue`) / template-edit (`ModuleFramePartiesPanel.vue`) / diff stepper (`PartyDiffCard.vue`) 三元件 render 同 5 個 party 欄位但獨立維護。§11.23 Bug B Issue 1 只 fix `PartyDiffCard.vue`，其他兩元件留缺。

### Bug H-5 — BE docx parser 解 email 漏掉（substring collision）

**症狀**：Bug H-4 fix 後 preview 出現「電子郵件」欄位但全空。

**Root cause**：`_PARTY_LABEL_MAP` substring matching ordering bug — label「e-Mail Address:」normalize 為 `'emailaddress'`，`'address' in 'emailaddress' → True` → 先匹中 `'address'` → mis-classify 成 address field → 因 Office Address row 已先填 `party['address']` → email value silently dropped。

**實證**：9 個 parse_job（140-148, fix 前）全部 `email_filled=0`，**從 2026-05-23 第一天就有的 bug，不是 regression**。今天才被看見是因為 Bug H-4 把 email input 顯式 render 出來。

**Fix**：重排 `_PARTY_LABEL_MAP` 把 email patterns 放在 address 之前 + 加註解警告 + 收緊現有 weak test（原本「any field 含 @」改成「email field 含 @」）+ 加 substring collision regression test。

### Bug H-6 — System-wide auto-fill org_unit_id 假鉤稽（jedi-common bug）

**症狀**：mf 372 template-edit「負責單位」tab 顯示屏東飛機維修廠 鉤稽到「System Administration Department」— user 沒手動鉤稽過。

**Root cause**：`jedi_common/session/database/db_mw.py:set_tenant_info_before_insert` 在 SQLAlchemy `before_flush` event hook 對所有 `session.new` 自動填 `obj.org_unit_id = user_ctx.org_unit_id`（admin = 98）。設計上是 tenant scope 用途，但 `oscal_parties` / `ssp_leveraged_authorizations` / `ssp_components` / `ssp_inventory_items` 的 `org_unit_id` 是**業務語意「鉤稽到哪個部門」**，兩種語意衝突。

**影響範圍**（fix 前 DB 證據）：
- 33/1137 person party `org_unit_id=98`（person 根本不該有 org_unit_id — UI 用 `matched_user_id` 鉤稽）
- 4/4 org party `org_unit_id IN (98, 99)`
- 36/36 leveraged_authorization `org_unit_id=98`
- 391/391 components `org_unit_id IN (98, 99)`
- 3/3 inventory_items `org_unit_id=98`

**Fix（Option D — opt-out flag）**：
1. jedi-common `db_mw.py` 加 `__skip_auto_org_unit__` class attribute check（其他 model 行為不變、保留 tenant_id auto-fill 不影響 RLS）
2. jedi-oscal 4 個 model 設 `__skip_auto_org_unit__ = True`

**髒資料清理**：單 transaction 把 4 表全部 `org_unit_id IS NOT NULL` 清成 NULL — 一共 467 row（37 party + 36 LA + 391 component + 3 inventory）。理由：所有非 NULL 值都 IN (98, 99) = 系統 admin/IT dept 自動填，沒一筆是 user 真正手動鉤稽過的。

---

## 2. Commits 清單（待 commit，user 拍板）

### 待 commit changes（working tree）

| Repo / 套件 | 檔案 | 內容 |
|---|---|---|
| **compliance-manager-fe** | `src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue` | Bug H-1: ROLE_LABEL「責任單位」→「負責單位」；Bug H-2/H-4: org + person card 各 +2 input（電話 + 地址）|
| **compliance-manager-fe** | `src/components/grc/ModuleFramePartiesPanel.vue` | Bug H-1: DataTable column「單位名稱」→「負責單位名稱」；Bug H-2/H-4: DataTable +2 column + Dialog +2 input |
| **compliance-manager-fe** | `src/config/locales/i18n/zh-tw/ssp-docx-import.json` | Bug H-1/H-3: section title「單位 / 人員」→「負責單位 / 參與人員」|
| **compliance-manager-fe** | `src/config/locales/i18n/zh-tw/module-frame.json` | Bug H-1/H-3: tab header「責任單位 / 責任人員」→「負責單位 / 參與人員」|
| **compliance-manager-be** | `domain/oscal/parser/docx_section_extractors.py` | Bug H-5: `_PARTY_LABEL_MAP` 重排 email 在 address 之前 + 註解警告 |
| **compliance-manager-be** | `tests/test_docx_section_extractors.py` | Bug H-5: 收緊現有 email test + 加 substring collision regression test |
| **compliance-manager-be** | `pyproject.toml` | Bug H-6: jedi-common 切 path dep（pin 註解掉）|
| **jedi-common** | `jedi_common/session/database/db_mw.py` | Bug H-6: `set_tenant_info_before_insert` 加 `__skip_auto_org_unit__` opt-out check |
| **jedi-oscal** | `jedi_oscal/infra/model/base/oscal_party.py` | Bug H-6: 加 `__skip_auto_org_unit__ = True` + 註解 |
| **jedi-oscal** | `jedi_oscal/infra/model/base/oscal_leveraged_authorization.py` | Bug H-6: 同上 |
| **jedi-oscal** | `jedi_oscal/infra/model/base/oscal_component.py` | Bug H-6: 同上 |
| **jedi-oscal** | `jedi_oscal/infra/model/base/oscal_inventory_item.py` | Bug H-6: 同上 |
| **DB**（已執行）| 4 表 `UPDATE org_unit_id = NULL`（467 row）| Bug H-6 髒資料清理 |

### 待寫 docs（commit 前補上）

- `docs/features/FR-028-2605-ssp-oscal-alignment/design.md` §11.26 (Bug H) / §11.27 (Bug H-5) / §11.28 (Bug H-6) — root cause / fix options trade-off / 教訓
- `docs/features/FR-028-2605-ssp-oscal-alignment/design.md` §11 index 行末加 §11.26 ~ §11.28 連結
- 髒資料清理 SQL 也存成 migration 或 fix script 留檔（`scripts/sql/2026-05-25-bug-h6-clear-auto-fill-org-unit-id.sql`）

### push 狀態

- **0 commit ahead origin** — 全部改動還在 working tree，等 user 拍板才 commit + push
- **jedi-common + jedi-oscal 不發 Nexus**（per CLAUDE.md「外部套件異動規範」dev 階段走 path dep，feature 全 verified ship 才發版）

---

## 3. Verify Evidence

### 3.1 BE pytest

```
tests/test_docx_section_extractors.py + tests/test_docx_section_extractors_customer.py
+ tests/test_ssp_docx_import_app_service.py + tests/test_ssp_write_strategy.py
+ tests/test_cmmc_ssp_adapter_v3_bundle.py + tests/test_ssp_excel_import_app_service.py
= 168 passed (含新 Bug H-5 regression test)
```

### 3.2 Bug H-5 — BE parser email 端到端

| parse_job | 時間 | email_filled / party_count | 備註 |
|---|---|---|---|
| 140-148 | 2026-05-24 13:06 ~ 17:46 | **0/5** ×9 | fix 前；substring collision 從 2026-05-23 就有 |
| **153** | 2026-05-25 02:23（fix 後）| **4/5** ✅ | 屏東 org 無 email 正確（docx 本來就沒填）；4 person email 全抓到 |

### 3.3 Bug H-6 — 鉤稽欄端到端

| 表 | fix 前 (auto-fill 值分布) | fix 後 (清理後) |
|---|---|---|
| oscal_parties (person) | 33 row org_unit_id=98 | 0 row has org_unit_id |
| oscal_parties (org) | 4 row org_unit_id IN (98, 99) | 0 |
| ssp_leveraged_authorizations | 36 row org_unit_id=98 | 0 |
| ssp_components | 391 row org_unit_id IN (98, 99) | 0 |
| ssp_inventory_items | 3 row org_unit_id=98 | 0 |

mf 372 (`2c4ba0b8-...`) template-edit 兩 tab：
- 「負責單位 (1)」屏東飛機維修廠：鉤稽欄「**未鉤稽**」（fix 前顯示「System Administration Department」假鉤稽）
- 「參與人員 (4)」4 row 全「**未鉤稽**」+ email/電話/地址完整保留

### 3.4 Bug H — Preview UI 5 card

`/module-frame/import-docx` 上傳亞航 docx 後預覽：
- section titles: `["負責單位", "參與人員"]` ✅
- org card 4 labels: 名稱 / 電子郵件 / 電話 / 地址 ✅
- person card 5 labels: 名稱 / 職稱 / 電子郵件 / 電話 / 地址 ✅
- 5 card 全顯示電話 / 地址 / email input value 正確 ✅
- console 0 errors / 11 pre-existing warnings (跟 Bug H 無關)

### 3.5 截圖（存 BE 根目錄）

| 檔案 | 內容 |
|---|---|
| `bug-h-template-edit-org-tab.png` | template-edit「負責單位 (1)」tab DataTable 顯示電話 / 地址 column |
| `bug-h-org-edit-dialog.png` | 「編輯單位」Dialog 含電話 + 地址 input |
| `bug-h-person-edit-dialog.png` | 「編輯參與人員」Dialog 含電子郵件 + 電話 + 地址 input |
| `bug-h-import-preview-parties-tab.png` | docx import preview 5 card 含電話/地址（fix 前狀態）|
| `bug-h5-preview-email-filled.png` | docx import preview 5 card email 補上後（fix 後狀態）|
| `bug-h6-cleanup-org-tab-no-link.png` | mf 372「負責單位」清理後「未鉤稽」|
| `bug-h6-cleanup-person-tab-no-link.png` | mf 372「參與人員」清理後 4 row 全「未鉤稽」|

---

## 4. 殘留 / 不在 scope

| 項目 | 處理 |
|---|---|
| en locale 4 個 key（section title + tab header）對應英文 label | 等 user 拍板英文才動 |
| `oscal-role.json responsible-organization.label`「專案負責單位」是否改「負責單位」 | 暫不動 — Tag 角色 label vs Tab 分類 label 視覺層級不同 |
| 三元件抽 `<PartyFieldRow>` 共用元件 refactor | future cleanup — scope 太大本期不動 |
| jedi-oscal `_upsert_responsible_party` silent-skip pattern | 同 §11.24 follow-up — jedi-oscal 進版時順手 |
| Bug G init_ap_controls silent failure | 已撤回（前 swap commit b7f02084）|
| Phase 5 OSCAL Export | Deferred |
| **L8 jedi-common + jedi-oscal 進版 + Nexus push** | User 拍板 — feature 全 verified 後一次 bump 兩套件版本 |
| **L9 BE/FE 版號對齊** | User 拍板 — 跟 L8 一起 |

---

## 5. 教訓（Bug H 整段 arc）

1. **三元件對齊 ≠ 一處改完**（Bug H）：preview / template-edit / diff stepper 三元件 render 同 party 欄位但獨立維護。Bug B Issue 1 只修 diff stepper 不夠 — 列入未來新增 party 元件 / 欄位的 checklist。
2. **i18n key value 改字串 ≠ 改 key 名**（Bug H）：本期只改 zh-tw 4 個 key value，vue 端 `t()` 呼叫 0 動。
3. **substring matching 的順序敏感性**（Bug H-5）：dict-as-priority-map 的 substring match 對 declaration order 敏感。「短 substring 排在長 substring 之前」就會偷走 match（`'address'` 偷 `'emailaddress'`）。Fix 是重排 + 加註解；若未來再加新 label，加 sort by length desc 更穩。Weak test 也是元凶：原本「any field 含 @」太鬆讓 bug 滑過 — 強 test 必精確指定 expected field。
4. **「label 雜亂 / fields 缺漏」其實也是 cross-source consistency bug**（Bug H + H-5）：跟 §11.24 Bug B / §11.25 Bug D 同概念不同 facet — 一個 user-facing 概念分散多個 surface 維護，沒 single source of truth 就會漂移。
5. **shared infra hook 的雙語意陷阱**（Bug H-6）：jedi-common `set_tenant_info_before_insert` 把 `org_unit_id` 當 tenant scope 自動填 — 對大多數 model 正確；但對 `oscal_parties` 等業務語意把 `org_unit_id` 當「鉤稽到的部門」的 model 是災難。**Hook 不能假設「同名欄位 = 同語意」**。Opt-out flag 是正確設計（caller-driven，不破壞 hook 原意）。
6. **隱形 bug 借 UI 補新欄位才浮出**（Bug H-5 + H-6）：H-5 從 2026-05-23 第一天就在，H-6 從更早就在（mf 367/368/369/370 全受影響）。直到 Bug H-4 把 email + 鉤稽欄位顯式 render 才被 user 看見。**「user 看不到的 bug 不是沒 bug，只是暫時沒人查」** — 強 verification + 廣泛 test coverage 才能提早 surface。
7. **「之前是好的」要實證、不能憑感覺**（Bug H-5）：user 直覺說「之前 email 能抓到」但 9 個歷史 parse_job 全部 email_filled=0 — 從 day 1 就壞。實證快速排除「regression」假設，省追舊版時間。
8. **跨套件 bug 也要動套件**（Bug H-6）：per memory `feedback_jedi_package_modify_allowed` —「正確修改 > 最小變更」。Bug H-6 root cause 在 jedi-common shared infra，不該為了「不動套件」就在主專案弄 wrapper / sidecar。dev 階段走 path dep 不每次發版（per memory `feedback_jedi_package_dev_path_first`）。

---

## 6. 給 user 的 hand-off summary

**已修 + 已驗收（user 手測通過）**：

1. FE 4 檔 — Bug H 純 UI（label + 電話 + 地址）
2. BE 2 檔 — Bug H-5 email parser + regression test
3. jedi-common 1 檔 + jedi-oscal 4 檔 — Bug H-6 opt-out flag
4. DB 4 表 467 row 清理 — Bug H-6 髒資料

**動作項（user 拍板）**：

1. push 順序建議（dev path dep 期間）：
   - 不要 push BE `pyproject.toml`（dev-only path dep 改動）
   - 等 jedi-common + jedi-oscal 全 verified → 一次性兩套件 bump version + 推 Nexus → BE `pyproject.toml` 改回 pin 新版本 → 跟 BE 改動一起 commit
2. **後續若 release** → 走 release SOP（release_note + bump pyproject.toml + FE 對齊版號）
3. 看 7 張截圖驗收 UI（特別 `bug-h6-cleanup-*` 確認鉤稽欄正確）

**測試新建殘留資料**：

- mf 367 ~ 372（含 mf 372 = `2c4ba0b8-...`、mf 369 = `f489a145-...`）為前後測試殘留，可手動刪
- parse_job 140 ~ 153 同樣為驗證痕跡

**待補 docs（commit 前補上）**：

- design.md §11.26 / §11.27 / §11.28
- §11 index 行末
- scripts/sql/2026-05-25-bug-h6-clear-auto-fill-org-unit-id.sql

---

## 7. 為何前期 Bug B / D fix 沒順手解 Bug H / H-5 / H-6

- **Bug H**：FE 三元件對齊 — Bug B Issue 1 只動 diff stepper，沒順手碰 preview / template-edit 兩元件。視角差異：Bug B 修「update mode 看不到既有欄位」，Bug H 修「create mode 看不到 BE 已有資料」。
- **Bug H-5**：substring collision — extractor 跟 Bug B / D 完全不同 code path。User 之前沒踩到是因為「電子郵件」label 顯式存在但 email value 被 silently mis-classify 成 address，且 weak test「any field 含 @」沒抓到。
- **Bug H-6**：jedi-common 共享 hook — 從 oscal table 第一天 ship 就埋著（2026-05-24 migration），但 person UI 不看 `org_unit_id` 鉤稽（用 `user_id`），org 也沒人特地檢查。借 Bug H-4 把鉤稽欄完整顯示才浮出來。

三段教訓共通：**user-facing concept 分散在多 surface（FE 元件 / BE adapter / shared infra hook）— 「一個概念對應一個來源」是 single source of truth 設計原則，違反就靠人眼追**。
