| 項目 | 內容 |
|---|---|
| 緣由 | Bug P/Q/R session 走完 fix + user 真實 reproduce 後,發現 Bug R 根本不是 wire 漏 1 行,是 diff_service.annotate_parse_result 對同 party_uid emit 兩個 entry (一個 changed、一個 gone),後續 unlink_parties 一次砍 8 個 link rows(含跨 context)— 嚴重 data corruption side effect |
| Branch | feature/ssp-oscal-alignment(接續 H-N + Bug O + P/Q arc,這是 Bug R 第二輪 deeper dive) |
| 接手前必讀 | 本文件自包含 + 上一份 P/Q/R handoff + Bug O FIXED-SUMMARY |
| 預估時間 | 4-8 小時(深度跨 diff_service / write_strategy / unlink 邏輯 + 跨 context_type 影響面) |
docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-p-q-r-parties-form-handoff.md — 了解 Bug P/Q/R session 的原始假設docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-o-FIXED-SUMMARY.md — Bug R 的 cache invalidate fix 是接續 Bug O annotate 改動app/oscal/service/ssp_docx_import_app_service.py — confirm_import (line 540~) / _filter_parties_for_write (line 1281~) / _load_current_parties / _collect_parties_to_unlinkapp/oscal/service/ssp_docx_diff_service.py — annotate_parse_result / _annotate_party (這條是真因區)domain/oscal/service/write_strategy/module_frame_write_strategy.py — write_parties / unlink_partiesapp/oscal/service/party_context_service.py — list_parties / update_party551c7dc Bug R cache invalidatefa43b79 Bug Q role Dropdown + Bug P button wordingPartiesSection.vue (Dropdown 改放 grid 內)、PartyLinkDialog.vue (拿掉 code filter)、SspDocxImportPage.vue (加 contentOverrides.parties collect)~/Projects/Billows/Audit-Manager/compliance-manager-fe/CLAUDE.md讀完跑 §6 Pre-flight + §7 Verify → §3 開工順位。
2c4ba0b8-52e2-4e90-a8fc-df8dcf481002 (AOO AOOA CMMC Level 1 導入計劃) → 批次維護 → 從文件匯入 → 上傳 ~/Desktop/亞航-CMMC-SSP-20260520-1會議討論版.docx/Users/chouraymond/.claude/image-cache/bfd49895-5cac-4f87-b6df-94379d7b0a8e/5.png)顯示參與人員 8 筆(原 4 + 新 4 同名但 role 不同的 row)[ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
'ecbe22dd-5ee8-4c43-aef3-6017f9eee9bb': 'use_docx',
'5a1c53b0-97d0-42c3-9d8e-e424ad69c364': 'use_docx',
'9c2c6408-3094-4dc4-b993-b5bb40800631': 'use_docx',
'f1d242db-ce61-49d6-866e-14dc97f4c71b': 'use_docx',
'9ef01730-631f-4c30-8e41-7bbdea8f362b': 'use_docx',
}
# 5 個 parsed parties annotated 第一輪 — diff_status 正常
[ssp-confirm] annotated party uid=ecbe22dd diff_status=unchanged decision=use_docx
[ssp-confirm] annotated party uid=5a1c53b0 diff_status=changed decision=use_docx
[ssp-confirm] annotated party uid=9c2c6408 diff_status=changed decision=use_docx
[ssp-confirm] annotated party uid=f1d242db diff_status=changed decision=use_docx
[ssp-confirm] annotated party uid=9ef01730 diff_status=changed decision=use_docx
# ⚠️ DUPLICATE!同 4 個 person uid 又出現第二次,這次標 diff_status=gone
[ssp-confirm] annotated party uid=5a1c53b0 diff_status=gone decision=use_docx
[ssp-confirm] annotated party uid=9c2c6408 diff_status=gone decision=use_docx
[ssp-confirm] annotated party uid=f1d242db diff_status=gone decision=use_docx
[ssp-confirm] annotated party uid=9ef01730 diff_status=gone decision=use_docx
# 接著 inject target_party_uid 對 5 個 matched parsed (含 1 org + 4 person)
[ssp-confirm] party uid=ecbe22dd: injecting target_party_uid=ecbe22dd for matched update
... (4 person 同樣 inject)
# 然後 4 個 gone person 走 unlink path
[ssp-confirm] party uid=5a1c53b0 gone+use_docx → unlink path (skipped from write)
[ssp-confirm] party uid=9c2c6408 gone+use_docx → unlink path (skipped from write)
[ssp-confirm] party uid=f1d242db gone+use_docx → unlink path (skipped from write)
[ssp-confirm] party uid=9ef01730 gone+use_docx → unlink path (skipped from write)
# resolved 5 parsed parties to write — 但其中 4 個也被排進 unlink
[ssp-confirm] _filter_parties_for_write resolved 5 parsed parties to write
[ssp-confirm] calling ModuleFrameWriteStrategy.write_parties with 5 parties...
[ssp-confirm] strategy.write_parties returned parties_written=5
# ⚠️ Unlink 一次砍 8 row(不是 4 — 為什麼?跨 context 越權砍?)
[ssp-confirm] calling ModuleFrameWriteStrategy.unlink_parties with 4 uids
[ssp-confirm] strategy.unlink_parties removed 8 link rows
-- mf 372 對應的 responsible_parties link:confirm 完只剩 1 個
SELECT rp.id, rp.role_id, rp.party_uuid, p.name, p.party_type
FROM oscal.oscal_responsible_parties rp
LEFT JOIN oscal.oscal_parties p ON p.uid::text = rp.party_uuid
WHERE rp.context_type = 'module_frame' AND rp.context_id = 372;
-- 結果:1 row (id=236, 屏東飛機維修廠, organization)
-- 預期:5 row (1 org + 4 person, 每個帶 user STEP 3 改的 role)
-- oscal_parties 最大 id 仍是 1275(confirm 沒新增 row)
SELECT MAX(id) FROM oscal.oscal_parties;
-- 結果:1275 (1271-1275 是 mf 370 亞航 E2E Excel 測試的,不是本次寫的)→ confirm 對 mf 372 只留 1 個 link (屏東飛機維修廠),4 個 person link 全被 unlink。 → BE write_parties 寫了 5 個(但寫到別處或 update 既有 row)+ unlink 8 個 link rows,結果 mf 372 context 內 4 person 全失。
POST /api/1.0/ssp-docx-import/120ab8a5-aad6-422d-8152-2b209d0ef105/confirm
原始 payload(未縮減,直接複製 user 提供內容):
{
"decisions": [
{
"control_id": "AC.L1-3.1.1",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" },
{ "objective_key": "(e)", "action": "use_docx" },
{ "objective_key": "(f)", "action": "use_docx" }
]
},
{
"control_id": "AC.L1-3.1.2",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" }
]
},
{
"control_id": "AC.L1-3.1.20",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" },
{ "objective_key": "(e)", "action": "use_docx" },
{ "objective_key": "(f)", "action": "use_docx" }
]
},
{
"control_id": "AC.L1-3.1.22",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" },
{ "objective_key": "(e)", "action": "use_docx" }
]
},
{
"control_id": "IA.L1-3.5.1",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" }
]
},
{
"control_id": "IA.L1-3.5.2",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" }
]
},
{
"control_id": "MP.L1-3.8.3",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" }
]
},
{
"control_id": "PE.L1-3.10.1",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" }
]
},
{
"control_id": "PE.L1-3.10.5",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" }
]
},
{
"control_id": "SC.L1-3.13.1",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" },
{ "objective_key": "(e)", "action": "use_docx" },
{ "objective_key": "(f)", "action": "use_docx" },
{ "objective_key": "(g)", "action": "use_docx" },
{ "objective_key": "(h)", "action": "use_docx" }
]
},
{
"control_id": "SC.L1-3.13.5",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" }
]
},
{
"control_id": "SI.L1-3.14.1",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" },
{ "objective_key": "(d)", "action": "use_docx" },
{ "objective_key": "(e)", "action": "use_docx" },
{ "objective_key": "(f)", "action": "use_docx" }
]
},
{
"control_id": "SI.L1-3.14.2",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" }
]
},
{
"control_id": "SI.L1-3.14.4",
"action": "use_docx",
"objectives": []
},
{
"control_id": "SI.L1-3.14.5",
"action": "use_docx",
"objectives": [
{ "objective_key": "(a)", "action": "use_docx" },
{ "objective_key": "(b)", "action": "use_docx" },
{ "objective_key": "(c)", "action": "use_docx" }
]
}
],
"parties_decisions": [
{ "party_uid": "ecbe22dd-5ee8-4c43-aef3-6017f9eee9bb", "action": "use_docx" },
{ "party_uid": "5a1c53b0-97d0-42c3-9d8e-e424ad69c364", "action": "use_docx" },
{ "party_uid": "9c2c6408-3094-4dc4-b993-b5bb40800631", "action": "use_docx" },
{ "party_uid": "f1d242db-ce61-49d6-866e-14dc97f4c71b", "action": "use_docx" },
{ "party_uid": "9ef01730-631f-4c30-8e41-7bbdea8f362b", "action": "use_docx" }
],
"manual_assignments": [],
"skipped_paragraph_idxs": [],
"predicted_controls_user_selection": [
"PE.L1-3.10.5", "AC.L1-3.1.22", "SI.L1-3.14.1", "SC.L1-3.13.5",
"AC.L1-3.1.2", "SI.L1-3.14.5", "PE.L1-3.10.1", "IA.L1-3.5.1",
"AC.L1-3.1.20", "IA.L1-3.5.2", "MP.L1-3.8.3", "SI.L1-3.14.4",
"SI.L1-3.14.2", "SC.L1-3.13.1", "AC.L1-3.1.1"
],
"source_uid": "2c4ba0b8-52e2-4e90-a8fc-df8dcf481002",
"content_overrides": {
"components": {
"1": { "status": "operational" },
"2": { "status": "operational" },
"3": { "status": "operational" }
},
"parties": {
"0": {
"address": "No. 330, Shengli Rd., Pingtung City, Pingtung County 900209, Taiwan (R.O.C.)",
"email_address": null,
"match_confidence": 0,
"match_method": "unmatched",
"matched_org_unit_id": null,
"matched_org_unit_label": null,
"matched_user_id": null,
"matched_user_label": null,
"name": "屏東飛機維修廠",
"party_type": "organization",
"remarks": null,
"role": "responsible-organization",
"short_name": null,
"target_party_uid": null,
"telephone_number": "08-7656111",
"title": null,
"party_uid": "ecbe22dd-5ee8-4c43-aef3-6017f9eee9bb"
},
"1": {
"address": "444 Salomon Circle, Sparks, NV 89434, USA",
"email_address": "info@sncorp.com",
"match_confidence": 0,
"match_method": "unmatched",
"matched_org_unit_id": null,
"matched_org_unit_label": null,
"matched_user_id": null,
"matched_user_label": null,
"name": "Sierra Nevada Corporation",
"party_type": "person",
"remarks": null,
"role": "system-owner",
"short_name": null,
"target_party_uid": null,
"telephone_number": "+1 775-356-6888",
"title": "NA",
"party_uid": "5a1c53b0-97d0-42c3-9d8e-e424ad69c364"
},
"2": {
"address": "No. 1050, Jichang Rd., Rende Dist., Tainan City 717206, Taiwan (R.O.C.)",
"email_address": "hmtseng@airasia.com.tw",
"match_confidence": 0,
"match_method": "unmatched",
"matched_org_unit_id": null,
"matched_org_unit_label": null,
"matched_user_id": null,
"matched_user_label": null,
"name": "曾漢明",
"party_type": "person",
"remarks": null,
"role": "system-security-officer",
"short_name": null,
"target_party_uid": null,
"telephone_number": "886-6-2681911 EXT 2563",
"title": "Engineer",
"party_uid": "9c2c6408-3094-4dc4-b993-b5bb40800631"
},
"3": {
"address": "No. 1050, Jichang Rd., Rende Dist., Tainan City 717206, Taiwan (R.O.C.)",
"email_address": "wywang@airasia.com.tw",
"match_confidence": 0,
"match_method": "unmatched",
"matched_org_unit_id": null,
"matched_org_unit_label": null,
"matched_user_id": null,
"matched_user_label": null,
"name": "王文堯",
"party_type": "person",
"remarks": null,
"role": "authorizing-official",
"short_name": null,
"target_party_uid": null,
"telephone_number": "886-6-2681911 EXT 2600",
"title": "Manager Information & Cyber Security",
"party_uid": "f1d242db-ce61-49d6-866e-14dc97f4c71b"
},
"4": {
"address": "No. 1050, Jichang Rd., Rende Dist., Tainan City 717206, Taiwan (R.O.C.)",
"email_address": "edward@airasia.com.tw",
"match_confidence": 0,
"match_method": "unmatched",
"matched_org_unit_id": null,
"matched_org_unit_label": null,
"matched_user_id": null,
"matched_user_label": null,
"name": "張才華",
"party_type": "person",
"remarks": null,
"role": "information-owner",
"short_name": null,
"target_party_uid": null,
"telephone_number": "886-6-2681911 EXT 2630",
"title": "Manager Information & Cyber Security",
"party_uid": "9ef01730-631f-4c30-8e41-7bbdea8f362b"
}
}
}
}Payload 觀察:
parties_decisions 5 個 uid 全 use_docx ✓content_overrides.parties[0~4] 5 個 party 整份內容,含 role 欄位、含 party_uid、含 target_party_uid:null、含 match_confidence:0 match_method:"unmatched"draft.parties 整份 (含所有欄位) 塞進 content_overrides.parties(mirror components / leveraged 既有 idx-keyed pattern)— 這條 wire 是對的,payload 證明 BE 拿到了完整 user 編輯資料content_overrides.components 3 個 idx 都帶 status: operational ✓system_characteristic_decision、components_decisions、leveraged_authorizations_decisions、inventory_items_decisions 4 個 v3 decision keys — 這是 FE store buildConfirmPayload 漏帶?還是 user 沒選所以 FE 跳過?需查| Screenshot | 路徑 | 顯示 |
|---|---|---|
| Image #5 (template-edit) | /Users/chouraymond/.claude/image-cache/bfd49895-5cac-4f87-b6df-94379d7b0a8e/5.png |
參與人員 (8) — 4 對重複 name 同 person,上半 4 個 role=系統使用者(舊),下半 4 個 role 是 user STEP 3 改的(系統所有者 / ISSO / AO / 資料所有者) |
| Image #6 (template-edit) | /Users/chouraymond/.claude/image-cache/bfd49895-5cac-4f87-b6df-94379d7b0a8e/6.png |
系統依賴元件 4 筆,「狀態」欄全部 (—) |
但 user image 跟我 DB query 結果不符 — DB 查 mf 372 link 表只 1 row。可能 image 是某個瞬間 stale,或 FE 從別處抓資料。這部分待 next session 親手 reproduce 對齊。
從 log §1.2 確認 — annotated_parse_result.parties 列表內 person 5a1c / 9c2c / f1d2 / 9ef0 各出現兩次:
diff_status=changeddiff_status=gone→ _filter_parties_for_write 對 use_docx + changed → write → 同個 uid 對 use_docx + gone → 走 _collect_parties_to_unlink 加進 unlink list → 同 uid 被同時 write + unlink → 結果 write 完又 unlink → 4 person 從 mf 372 context 消失
Verify 命令:
# 找 ssp_docx_diff_service.annotate_parse_result 邏輯
grep -n "def annotate_parse_result\|def _annotate_party\|diff_status=.gone\|diff_status=.changed" \
~/Projects/Billows/Audit-Manager/compliance-manager-be/app/oscal/service/ssp_docx_diff_service.py關鍵讀 _annotate_party 看「matched」跟「current orphan」是否該共用同 uid 或不該共用:
_load_current_parties(effective_source_uid, job.source_type='module_frame') 拿 mf 372 既有 parties。但實際 DB query mf 372 link 表只 1 row(屏東飛機維修廠)— 為什麼 annotate 看到 4 person 在 current?
可能:
_load_current_parties 不只讀 responsible_parties link,還讀 oscal_parties 全表Verify 命令:
grep -n "def _load_current_parties\|_load_current" \
~/Projects/Billows/Audit-Manager/compliance-manager-be/app/oscal/service/ssp_docx_import_app_service.pyLog: strategy.unlink_parties removed 8 link rows — caller 給 4 uids,但移除 8 個 link rows。可能:
→ 嚴重 data corruption,會把同個 party_uid 在其他 mf / ssp 的 link 也砍掉。
Verify 命令:
grep -n "def unlink_parties\|delete.*party_uuid\|DELETE FROM.*responsible_part" \
~/Projects/Billows/Audit-Manager/compliance-manager-be/domain/oscal/service/write_strategy/module_frame_write_strategy.py \
~/Projects/Billows/Audit-Manager/compliance-manager-be/app/oscal/service/party_context_service.py-- 看 unlink 前/後 link 表 row count 變化
-- (上面 log 顯示 confirm 之後 link 表只剩 53-8=45 row?看 53 是 mf 368-378 全部,砍 8 是哪 8 個)
PSQL ... -c "
SET app.is_super_admin='t';
SELECT context_type, context_id, count(*)
FROM oscal.oscal_responsible_parties
WHERE party_uuid IN ('5a1c53b0...', '9c2c6408...', 'f1d242db...', '9ef01730...')
GROUP BY context_type, context_id ORDER BY count desc;
"write_parties 寫了 5 個 oscal_parties row,但 _link_party (per party_context_service.py:add_party) 只在 add_party 才 call — write_parties 是 docx import path,是否漏 link 步驟?
Verify:grep ModuleFrameWriteStrategy.write_parties 內是否真有寫 responsible_parties 步驟。
ssp_docx_diff_service.annotate_parse_result + _annotate_party — 找出為什麼 same uid emit 兩次(30-60 分鐘)_load_current_parties — 確認 module_frame source 真實讀哪張表(15 分鐘)unlink_parties / delete by party_uuid — 看 scope 是否帶 context_type+context_id(15 分鐘)ModuleFrameWriteStrategy.write_parties 看是否真寫 link(15 分鐘)_apply_v2_bundle_overrides 對 components.{idx}.status 沒套,或 components write_strategy 漏存 status 欄位feedback_wait_for_user_command_to_close)| 檔案 | 為何 read |
|---|---|
app/oscal/service/ssp_docx_diff_service.py |
候選 A 核心 — annotate_parse_result 為何 same uid emit 兩次 |
app/oscal/service/ssp_docx_import_app_service.py:_load_current_parties |
候選 B — current_parties 從哪 load |
app/oscal/service/ssp_docx_import_app_service.py:_collect_parties_to_unlink (line 1392+) |
看 unlink 候選 list 怎麼產生 |
app/oscal/service/party_context_service.py:list_parties / remove_party |
GET parties 真實 query + 候選 C unlink scope |
domain/oscal/service/write_strategy/module_frame_write_strategy.py |
write_parties / unlink_parties 邏輯 |
domain/oscal/service/write_strategy/ssp_write_strategy.py |
對比 SSP 版本是否同樣問題 |
infra/oscal/repo/oscal_responsible_party_repo_impl.py (or 對應 path) |
看 unlink/delete SQL 是否帶 context_type+context_id WHERE |
| 檔案 | 為何 read |
|---|---|
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue (uncommitted change) |
看 contentOverrides.parties wire 是否該保留(payload 證明 FE 送對了 → 應該保留) |
compliance-manager-fe/src/stores/sspDocxImportStore.js |
看 buildConfirmPayload 為何 v3 decisions 沒送(components_decisions / leveraged_decisions 等都缺) |
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue (uncommitted change) |
Bug Q Dropdown grid 內 layout — 保留 |
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartyLinkDialog.vue (uncommitted change) |
Bug P 拿掉 code filter — 保留 |
compliance-manager-fe/src/composables/useModuleFrameParties.js |
cache 邏輯(這條沒問題) |
~/Projects/Billows/Audit-Manager/compliance-manager-fe/CLAUDE.mdfeedback_be_restart_after_service_change)# 1. 兩 repo branch + working tree
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be branch --show-current
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe branch --show-current
# 預期:兩個都 feature/ssp-oscal-alignment
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be status --short
# 預期:M pyproject.toml + 多個 .png screenshots + .playwright-mcp/*.docx — 都是 dev artifact
# 重點:app/oscal/service/ssp_docx_import_app_service.py 應該 NOT 出現(revert 完)
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe status --short
# 預期:3 個 M 檔
# M src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
# M src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue
# M src/components/grc/ssp-docx-import-v2/sections/PartyLinkDialog.vue
# 這 3 個是本 session 待 commit 的改動(layout + code filter fix + contentOverrides.parties wire),等 user 拍板再 commit / amend
# 2. local vs origin
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be log origin/feature/ssp-oscal-alignment..HEAD --oneline
# 預期:1 commit (0dac0d37 Bug O 收尾橫向文件) — 之前已 push 多個
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe log origin/feature/ssp-oscal-alignment..HEAD --oneline
# 預期:2 commits (fa43b79 Bug Q+P, 551c7dc Bug R cache) — 上 session ship 沒 push
# 3. BE listener (上 session 砍掉了,user 應重啟過)
lsof -t -i:8000 && echo "BE up" || echo "BE down — restart needed"
# 4. BE pytest smoke
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
poetry run pytest tests/test_ssp_docx_import_app_service.py \
tests/test_ssp_docx_diff_service.py \
tests/test_ssp_write_strategy.py -q
# 預期:192 passed (跟上 session 一致)# 6.1 H-6 cleanup verify — 4 OSCAL 表 org_unit_id 應全 NULL
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -c "
SET app.is_super_admin='t';
SELECT 'parties' AS tbl, count(*) AS bad FROM oscal.oscal_parties WHERE org_unit_id IS NOT NULL
UNION ALL SELECT 'leveraged', count(*) FROM oscal.ssp_leveraged_authorizations WHERE org_unit_id IS NOT NULL
UNION ALL SELECT 'components', count(*) FROM oscal.ssp_components WHERE org_unit_id IS NOT NULL
UNION ALL SELECT 'inventory', count(*) FROM oscal.ssp_inventory_items WHERE org_unit_id IS NOT NULL;
"
# 預期 4 row all bad=0
# 6.2 確認 mf 372 既有 link 狀態
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -c "
SET app.is_super_admin='t';
SELECT rp.id, rp.role_id, rp.party_uuid, p.name, p.party_type
FROM oscal.oscal_responsible_parties rp
LEFT JOIN oscal.oscal_parties p ON p.uid::text = rp.party_uuid
WHERE rp.context_type = 'module_frame' AND rp.context_id = 372
ORDER BY rp.id;
"
# 預期:1 row only (id=236, 屏東飛機維修廠) — 證實上 session confirm 後 4 person link 全失
# 6.3 看 53 row 全部跨 context 分佈
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -c "
SET app.is_super_admin='t';
SELECT context_type, context_id, count(*)
FROM oscal.oscal_responsible_parties
WHERE party_uuid IN (
'5a1c53b0-97d0-42c3-9d8e-e424ad69c364',
'9c2c6408-3094-4dc4-b993-b5bb40800631',
'f1d242db-ce61-49d6-866e-14dc97f4c71b',
'9ef01730-631f-4c30-8e41-7bbdea8f362b'
)
GROUP BY context_type, context_id ORDER BY context_id;
"
# 看 unlink 砍了哪些 context(候選 C verify)feedback_no_branch_switch)feedback_stage_commit_no_ask)feedback_be_restart_after_service_change)oscal_parties + oscal_responsible_parties 兩張feedback_verify_db_state_before_writing_fixes)— 不要靠直覺feedback_plan_vs_reality_verify_first)— 寫 plan 用的 method / table / signature 都先 grep 確認feedback_wait_for_user_command_to_close)— 寫 design §11.X / changelog / FIXED-SUMMARY 全部等 user 明確下令feedback_no_chinglish)docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-XX-bug-r-deeper-FIXED-SUMMARY.mdfeedback_annotate_unique_entity_per_uid.md — annotate 同 uid 不該 emit 多次 entryfeedback_unlink_scope_must_match_context.md — delete by uid 必須帶 context_type+context_id WHEREdocs/conversation-history/2026-05-XX/bug-r-deeper/無 — BE 改動已 revert 乾淨。
| Hash | Type | 內容 | 是否該保留 |
|---|---|---|---|
551c7dc |
fix | Bug R cache invalidate(confirm 後 invalidateMfParties / invalidateSspParties) | 保留 — payload 證明 wire 正確,cache 確實在 confirm 後被 invalidate |
fa43b79 |
fix | Bug Q (PartiesSection 補 role Dropdown) + Bug P (button wording 對 type 分流 + 5 i18n keys + icon 分流) | 保留 — UI 改動 user 已確認 layout 1/2 OK |
| File | 改動 | 是否該保留 |
|---|---|---|
SspDocxImportPage.vue |
confirm payload 加 if (draft.parties) contentOverrides.parties = {idx: {...p}} |
保留 — payload 證明 wire 正確帶 5 個 party 的 role + 其他欄位到 BE。但 next session 可能要看是否該瘦身(只送 changed field 而非整份) |
PartiesSection.vue |
角色 Dropdown 從頂部搬進 grid 內地址右側 + read-only mode label 對齊 + Dropdown class 改 w-full | 保留 — user image 確認 OK |
PartyLinkDialog.vue |
拿掉 ORG_UNITS filter 內 code: 欄位(Bug P 真實 400 root cause) |
保留 — BE OrgUnitQueryRequest schema 沒 code 欄位,留會 400 |
| File | 改動 |
|---|---|
~/.claude/projects/-Users-...memory/feedback_wait_for_user_command_to_close.md |
新檔,新規範「收尾等 user 下令」 |
~/.claude/projects/-Users-...memory/MEMORY.md |
index 加 1 行 |
compliance-manager-be/CLAUDE.md |
「做 summary」段前面加新規範條文 |
→ 這 3 個改動上 session 未 commit(user 沒明示要 commit)。next session 進 working tree 會看到,可保留或一起 commit。
| File | 內容 | 動作建議 |
|---|---|---|
docs/features/FR-028-2605-ssp-oscal-alignment/design.md |
§11.35 / §11.36 / §11.37 + §11 index 加 3 條 | 可保留,但 §11.37 內容是「Bug R = cache invalidate fix」已經錯了(真因不是 cache)— next session 該改寫 §11.37 為「Bug R 第一輪 fix (cache invalidate) 部分正確,但真因是 diff_service duplicate entry,見 §11.38」 |
docs/changelog/2026-05-25-fix-bug-pqr-parties-form.md |
涵蓋 Bug P/Q/R 3 個 fix | 可保留,但 Bug R 段落該標 partial fix + 引 §11.38 |
docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-pqr-FIXED-SUMMARY.md |
本期 SUMMARY | 可保留,但 Bug R 段該改 partial + 引本 handoff |
docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-pqr.md |
plan copy | 保留 archive |
0dac0d37 docs(ssp-oscal-alignment): Bug O 收尾橫向文件更新 + CLAUDE.md 收尾流程加「回頭更新文件」步驟 (origin 已有?或 ahead 1 — pre-flight verify)
c502ce08 docs(conversation-history): 2026-05-25 — H-N arc 4 sessions + Bug O 接手 1 session (已 push)
058df1ab docs(ssp-oscal-alignment): Bug O Phase F 收尾 — design §11.34 closed + 補 5 教訓 + plan / changelog / SUMMARY / handoff P-Q-R
b0032e46 fix(ssp-oscal-alignment): Bug O regression — 4 v3 list annotated 用獨立 _diff key + GET 路徑漏 wire
bedbb91c feat(ssp-oscal-alignment): Bug O Phase B — BE confirm 接 4 個 v3 decisions filter(含 overwrite caveat)
fa43b79 fix(ssp-oscal-alignment): Bug Q + Bug P — PartiesSection 補 role Dropdown + button wording 對 party_type 分流 (本 session, 未 push)
551c7dc fix(ssp-oscal-alignment): Bug R — confirm 後 invalidate parties cache (本 session, 未 push)
5f6458b fix(ssp-oscal-alignment): Bug O regression + 收尾 UI 微調 (已 push)
061a742 feat(ssp-oscal-alignment): Bug O Phase E — DiffResolutionStep TabView refactor
7e34aeb feat(ssp-oscal-alignment): Bug O Phase D — FE 4 個新 DiffSection + 3 個 DiffCard 元件
請閱讀交接文件,了解現況,並接手 Bug R deeper dive。
路徑:docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-r-diff-service-duplicate-entry-handoff.md
按本檔 §0 接手讀序執行 — 必跑 §5 Pre-flight + §6 Verify mf 372 link 狀態 + 取 53 row 跨 context 分佈 → §3 開工順位。
簡述:Bug R 真因不是 cache,是 diff_service.annotate_parse_result 對同 party_uid emit 兩次 entry (一次 changed 一次 gone),加上 unlink_parties 一次砍 8 個 link 跨 context — 嚴重 data corruption。本檔 §1.2 有完整 BE log evidence,§1.4 有 user 真實 confirm payload,§2 有 4 個候選 root cause + verify 命令。
注意 §7 行為規範:不切 branch / 不自動 push / 跨 repo 改 FE 必 read CLAUDE.md / 改 BE service 必提醒重啟 / 收尾等 user 下令 / 人工驗證就切人工。