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 causejedi_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_itemsorg_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_insertorg_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 設計原則,違反就靠人眼追