| 項目 | 內容 |
|---|---|
| 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 |
| 子 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-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。
症狀: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 證據):
org_unit_id=98(person 根本不該有 org_unit_id — UI 用 matched_user_id 鉤稽)org_unit_id IN (98, 99)org_unit_id=98org_unit_id IN (98, 99)org_unit_id=98Fix(Option D — opt-out flag):
db_mw.py 加 __skip_auto_org_unit__ class attribute check(其他 model 行為不變、保留 tenant_id auto-fill 不影響 RLS)__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 真正手動鉤稽過的。
| 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/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 連結scripts/sql/2026-05-25-bug-h6-clear-auto-fill-org-unit-id.sql)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)
| 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 全抓到 |
| 表 | 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:
/module-frame/import-docx 上傳亞航 docx 後預覽:
["負責單位", "參與人員"] ✅| 檔案 | 內容 |
|---|---|
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 全「未鉤稽」 |
| 項目 | 處理 |
|---|---|
| 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 一起 |
t() 呼叫 0 動。'address' 偷 'emailaddress')。Fix 是重排 + 加註解;若未來再加新 label,加 sort by length desc 更穩。Weak test 也是元凶:原本「any field 含 @」太鬆讓 bug 滑過 — 強 test 必精確指定 expected field。set_tenant_info_before_insert 把 org_unit_id 當 tenant scope 自動填 — 對大多數 model 正確;但對 oscal_parties 等業務語意把 org_unit_id 當「鉤稽到的部門」的 model 是災難。Hook 不能假設「同名欄位 = 同語意」。Opt-out flag 是正確設計(caller-driven,不破壞 hook 原意)。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)。已修 + 已驗收(user 手測通過):
動作項(user 拍板):
pyproject.toml(dev-only path dep 改動)pyproject.toml 改回 pin 新版本 → 跟 BE 改動一起 commitbug-h6-cleanup-* 確認鉤稽欄正確)測試新建殘留資料:
2c4ba0b8-...、mf 369 = f489a145-...)為前後測試殘留,可手動刪待補 docs(commit 前補上):
org_unit_id 鉤稽(用 user_id),org 也沒人特地檢查。借 Bug H-4 把鉤稽欄完整顯示才浮出來。三段教訓共通:user-facing concept 分散在多 surface(FE 元件 / BE adapter / shared infra hook)— 「一個概念對應一個來源」是 single source of truth 設計原則,違反就靠人眼追。