# Bug S/T/U Implementation Plan — Components silent-fail + keep_current 砍鉤稽 link + FE 漏 v3 decisions

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

| 項目 | 內容 |
|---|---|
| 緣由 | 2026-05-26 user 用 mf b8065ad5 + 亞航-CMMC-SSP-20260520-1.docx 跑 template-edit docx import，三個問題：(S) components/leveraged 沒匯入 DB（DB 0 row 但 docx 解出 4+1）；(T) 部分 STEP 2 選 keep_current 的 party 鉤稽（responsible_party link）被砍；(U) FE confirm payload 完全沒帶 4 個 v3 decision keys |
| Branch | `feature/ssp-oscal-alignment` |
| Bug 編號 | S = stale-link cleanup 砍 keep_current；T = v2-bundle silent fail；U = FE 漏 v3 decisions wire |
| 預估時間 | 4-6 小時（含 Phase 0 verify + 3 個 fix + manual E2E） |

**Goal:** 修 docx import → mf template-edit 流程的 3 個 bug，讓 user 在 STEP 2 選的 use_docx / keep_current 決策都正確生效，且 components / leveraged / inventory 真實寫入 DB。

**Architecture:** Phase 0 先 patch BE 加 debug log + 請 user 重啟 + 重做一次 import 才 pin Bug T 確切位置；Phase 1-3 順序 fix S/T/U；Phase 4 manual E2E；Phase 5 收尾（**等 user 下令才 commit 收尾文件**）。

**Tech Stack:** Python 3.11 / Flask / SQLAlchemy / dependency-injector / Vue 3 / Pinia

---

## Pre-flight Commands（必跑）

```bash
# 1. branch + working tree
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be branch --show-current
# 預期：feature/ssp-oscal-alignment
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 | grep -E '\.py$|\.md$' | head -20
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe status --short

# 2. BE listener
lsof -t -i:8000 && echo "BE up" || echo "BE down — restart needed"

# 3. BE smoke tests
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 tests/test_module_frame_write_strategy_v2_parties.py -q
# 預期：全 pass
```

---

## Verify Hypotheses（plan 開工前必跑，per memory `feedback_plan_vs_reality_verify_first`）

```bash
# A. 確認 mf 343 + SSP shell 219/228 + components/leveraged 0 row
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -F$'\t' -c "
SET app.is_super_admin='t';
SELECT mf.id, mf.uid, mf.name FROM public.module_frames mf WHERE mf.uid='b8065ad5-e169-4df4-9bd3-5ec0d03b2e1d';
SELECT ssp.id, ssp.uid FROM oscal.system_security_plans ssp WHERE ssp.template_module_frame_id=343;
SELECT 'comp' k, count(*) FROM oscal.ssp_components WHERE ssp_id IN (219,228);
SELECT 'la' k, count(*) FROM oscal.ssp_leveraged_authorizations WHERE ssp_id IN (219,228);
SELECT 'rp_mf' k, count(*) FROM oscal.oscal_responsible_parties WHERE context_type='module_frame' AND context_id=343;
"
# 預期：mf=343, shell=219+228, comp=0, la=0, rp_mf=5

# B. 確認 parse_job 223 含 v2-bundle + components=4 + leveraged=1
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -F$'\t' -c "
SET app.is_super_admin='t';
SELECT id, status, (parsed_result->>'schema_version') schema,
       jsonb_array_length(parsed_result->'components') comp_n,
       jsonb_array_length(parsed_result->'leveraged_authorizations') la_n
  FROM oscal.ssp_docx_parse_jobs WHERE id=223;
"
# 預期：comp_n=4, la_n=1, schema=v2-bundle

# C. 確認 ModuleFrameWriteStrategy.write_parties 仍有 L539-544 stale-link cleanup
grep -nA 6 'existing_link_map.items' ~/Projects/Billows/Audit-Manager/compliance-manager-be/domain/oscal/strategy/module_frame_write_strategy.py
# 預期：line 540-544 顯示 `if uuid not in processed_uuids: delete_by_id`

# D. 確認 FE SspDocxImportPage L736-744 payload 仍漏 4 個 v3 keys
grep -A 10 'const payload = {' ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
# 預期：只看到 decisions / parties_decisions，沒看到 components_decisions / la_decisions / inventory_items_decisions / system_characteristic_decision

# E. 確認 sspDocxImportStore.buildConfirmPayload 已正確帶 4 個 v3 keys（之後 FE 改用它）
grep -nA 6 'buildConfirmPayload' ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/stores/sspDocxImportStore.js
# 預期：line ~214 包 components_decisions / leveraged_authorizations_decisions / inventory_items_decisions / system_characteristic_decision
```

任一條 verify 不符 → 停下回報 user，不開工。

---

## Phase 0: Pin Bug T silent-fail 確切位置（必跑）

> Bug T silent-fail 在 `confirm_service.confirm()` 內某處，inline reproduction 失敗（DI deepcopy block），改 patch BE 加 5 行 debug log 後重啟 → 請 user 重做一次 import → 看 log。

### Task 0.1: Patch `_run_v2_bundle_confirm` 加 debug log

**Files:**
- Modify: `app/oscal/service/ssp_docx_import_app_service.py:791-897`

- [ ] **Step 1: 在 `_run_v2_bundle_confirm` 進入點 + ensure_shell 後 + confirm 前後加 logger.info**

加在 L819 `if self._confirm_service is None:` 之前：

```python
self._safe_log_info(
    "[v2-bundle-debug] _run_v2_bundle_confirm enter: confirm_service=%s shell_service=%s "
    "source_type=%s source_uid=%s parsed_components=%d parsed_la=%d parsed_inv=%d sc=%s",
    "wired" if self._confirm_service is not None else "None",
    "wired" if self._ssp_shell_service is not None else "None",
    source_type,
    effective_source_uid,
    len(parsed_result.get("components") or []),
    len(parsed_result.get("leveraged_authorizations") or []),
    len(parsed_result.get("inventory_items") or []),
    "present" if parsed_result.get("system_characteristic") else "None",
)
```

加在 L865 `if ssp_id is None:` 之前（ensure_shell 成功時記下 ssp_id）：

```python
if source_type == "module_frame" and ssp_id is not None:
    self._safe_log_info(
        "[v2-bundle-debug] ensure_shell OK ssp_id=%s", ssp_id,
    )
```

加在 L872 `result = self._confirm_service.confirm(...)` 前後：

```python
self._safe_log_info(
    "[v2-bundle-debug] calling _confirm_service.confirm ssp_id=%s parsed_components=%d parsed_la=%d parsed_inv=%d",
    ssp_id,
    len(parsed_result.get("components") or []),
    len(parsed_result.get("leveraged_authorizations") or []),
    len(parsed_result.get("inventory_items") or []),
)
result = self._confirm_service.confirm(
    parsed_result=parsed_result,
    ssp_id=ssp_id,
    catalog_id=catalog_id,
    user_context=user_context,
)
self._safe_log_info(
    "[v2-bundle-debug] _confirm_service.confirm returned: %s", result,
)
```

- [ ] **Step 2: Patch `SspImportConfirmService.confirm()` 加 step-by-step trace**

**Files:**
- Modify: `domain/oscal/import_pipeline/confirm_service.py:41-115`

加在 L52 `bundle = dict_to_bundle(parsed_result)` 後 + L84 / L89 / L98 write 之前：

```python
logger.info(
    "[v2-bundle-debug] confirm: bundle.parsed_components=%d parsed_la=%d parsed_inv=%d parsed_parties=%d",
    len(bundle.parsed_components),
    len(bundle.parsed_leveraged_authorizations),
    len(bundle.parsed_inventory_items),
    len(bundle.parsed_parties),
)
# ... existing normalize ...
logger.info(
    "[v2-bundle-debug] confirm post-normalize: bundle.parsed_components=%d parsed_la=%d parsed_inv=%d",
    len(bundle.parsed_components),
    len(bundle.parsed_leveraged_authorizations),
    len(bundle.parsed_inventory_items),
)
```

並在每個 `self._leveraged.write(...)` / `self._component.write(...)` / `self._inventory.write(...)` 後 log return value。

- [ ] **Step 3: 通知 user**

```
請重啟 BE（patch 加了 debug log）。然後請：
1. 用 mf b8065ad5 跑一次 import-docx 上傳同一份 docx
2. STEP 2 全選「採用新值」
3. STEP 3 確認後按確認匯入
我會從 log 找確切 silent fail 位置。
```

- [ ] **Step 4: 收到 user OK 後 grep log**

```bash
grep -nE '\[v2-bundle-debug\]' ~/Projects/Billows/Audit-Manager/compliance-manager-be/log/app.log | tail -30
```

從 log 看出：
- (a) `_run_v2_bundle_confirm enter` 顯示 parsed_components 是否非 0
- (b) ensure_shell ssp_id 是 219 還是 228
- (c) `confirm` enter 跟 post-normalize 之間 bundle.parsed_components 變化
- (d) leveraged/component write return value

不 commit 這個 patch（debug 用，root cause pin 後拿掉，per Task 2.5）。

---

## Phase 1: Fix Bug S — ModuleFrameWriteStrategy stale-link cleanup 砍 keep_current

> `_filter_parties_for_write` 對 keep_current 不收進 list（只收 use_docx），但 `write_parties` L539-544 把 `existing_link_map` 內所有未在 `processed_uuids` 的 link 砍掉 — 副作用是 mix-decision 場景（部分 use_docx 部分 keep_current）會誤砍 keep_current 的 link。

**Files:**
- Modify: `domain/oscal/strategy/module_frame_write_strategy.py:471-546`
- Test: `tests/test_module_frame_write_strategy_v2_parties.py`

### Task 1.1: 寫 failing test

- [ ] **Step 1: 加 test case 證明 stale-link 誤砍**

```python
# tests/test_module_frame_write_strategy_v2_parties.py 內加
def test_write_parties_does_not_unlink_existing_when_called_with_subset(
    strategy, mock_mf, mock_party_repo, mock_responsible_party_repo,
):
    """write_parties 收到部分 parties 不該砍掉 existing_link 內未提交的 link。

    場景：mf 已有 3 個 parties (A, B, C)。Caller 只傳 A 進 write_parties
    （B/C 是 keep_current，由 caller 過濾掉）。預期：A 被 upsert，B/C link
    保留。Bug S：原本 L539-544 cleanup 把 B/C link 都砍掉。
    """
    # arrange existing 3 links (A, B, C)
    from jedi_oscal.domain.entity.base.oscal_responsible_party_entity import ResponsiblePartyEntity
    mock_responsible_party_repo.get_all.return_value = [
        MagicMock(id=1, party_uuid="uuid-A"),
        MagicMock(id=2, party_uuid="uuid-B"),
        MagicMock(id=3, party_uuid="uuid-C"),
    ]
    # _upsert_party returns existing party A entity
    mock_party_repo.get_one.return_value = MagicMock(uid="uuid-A")

    # act: only A is in parties list
    from domain.oscal.parser.ssp_intermediate import ParsedParty
    parties = [ParsedParty(name="A", party_type="person", role="system-owner")]
    written = strategy.write_parties(parties, source_uid=mock_mf.uid, user_id="tester")

    # assert: B/C delete_by_id 不該被呼叫
    delete_calls = mock_responsible_party_repo.delete_by_id.call_args_list
    deleted_ids = [c.args[0] for c in delete_calls]
    assert 2 not in deleted_ids, "B link should NOT be unlinked (keep_current)"
    assert 3 not in deleted_ids, "C link should NOT be unlinked (keep_current)"
    assert written == 1
```

- [ ] **Step 2: 跑 test 確認 FAIL**

```bash
poetry run pytest tests/test_module_frame_write_strategy_v2_parties.py::test_write_parties_does_not_unlink_existing_when_called_with_subset -v
```

預期：FAIL（delete_by_id 被 call 砍 B/C link）

### Task 1.2: Fix — 移除 stale-link cleanup（caller-driven unlink 已由 `_collect_parties_to_unlink` 處理）

- [ ] **Step 1: 刪除 `ModuleFrameWriteStrategy.write_parties` L507-518, L521, L535-536, L539-544 的 existing_link_map 跟 processed_uuids 邏輯**

修改後的 method body（L506-546 替換為）：

```python
        written = 0
        for parsed in parties:
            party_entity = self._upsert_party(parsed, user_id)
            if party_entity is None:
                continue
            party_uid = str(
                getattr(party_entity, "uid", None) or getattr(party_entity, "uuid", "") or ""
            )
            self._upsert_responsible_party(
                role_id=parsed.role or "",
                party_uid=party_uid,
                context_type="module_frame",
                context_id=module_frame_id,
            )
            written += 1

        # Note (Bug S fix 2026-05-26): 不再做 stale-link cleanup。caller
        # (_collect_parties_to_unlink) 已明確收集 diff_status='gone' + action='use_docx'
        # 的 party_uids 走 unlink_parties path。在 write_parties 內依「沒在
        # processed_uuids」推論 stale 是錯的 — 因為 caller 對 keep_current 的
        # party 也不會送進 parties list（用 SspWriteStrategy 同樣 pattern，
        # 它本來就沒這個 cleanup）。
        return written
```

對應拿掉 `from jedi_oscal.domain.entity.base.oscal_responsible_party_query_entity import ResponsiblePartyQueryEntity` import（如果本 method 不再用）— grep 確認 import 仍被其他 method 用（unlink_parties 用到）→ **保留 import**。

- [ ] **Step 2: 跑 Task 1.1 test 確認 PASS**

```bash
poetry run pytest tests/test_module_frame_write_strategy_v2_parties.py::test_write_parties_does_not_unlink_existing_when_called_with_subset -v
```

預期：PASS

- [ ] **Step 3: 跑全 test 確認沒退化**

```bash
poetry run pytest tests/test_module_frame_write_strategy_v2_parties.py tests/test_ssp_docx_import_app_service.py tests/test_ssp_write_strategy.py -q
```

預期：全 pass

### Task 1.3: Commit Bug S fix

- [ ] **Step 1: stage + commit（**只 commit 本 phase 改動，不混 debug log patch / 其他 phase**）**

```bash
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be add \
  domain/oscal/strategy/module_frame_write_strategy.py \
  tests/test_module_frame_write_strategy_v2_parties.py
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be commit -m "$(cat <<'EOF'
fix(ssp-oscal-alignment): Bug S — ModuleFrameWriteStrategy.write_parties 不再砍 keep_current link

Root cause: L539-544 stale-link cleanup 對 caller 沒送進來的 party_uuid
直接 delete_by_id — caller (`_filter_parties_for_write`) 對 keep_current
party 不收進 list 是預期行為，cleanup 卻把這些「沒寫入」誤判為「該砍」。
副作用：mix-decision 場景（部分 use_docx + 部分 keep_current）會把
keep_current 的 responsible_party link 砍掉 → user 在 template-edit
看不到 → 以為「鉤稽資料被刪除」。

Fix: 拿掉 existing_link_map / processed_uuids stale cleanup。caller 端
`_collect_parties_to_unlink` 已明確收集 diff_status='gone' + action='use_docx'
走 unlink_parties path，write_parties 不該自作主張砍 link
（mirror SspWriteStrategy.write_parties — 本來就沒這個 cleanup）。

Test: tests/test_module_frame_write_strategy_v2_parties.py 加
test_write_parties_does_not_unlink_existing_when_called_with_subset。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Phase 2: Fix Bug T — v2-bundle silent fail（confirm_service 寫 0 row）

> **必前置完成 Phase 0** — 拿到 debug log 確切位置才能寫 fix。

### Task 2.1: 根據 Phase 0 log 寫 failing test

- [ ] **Step 1: 識別 silent fail 位置**

從 Phase 0 grep 結果應該看到下列其中一條：

| 觀察 | Root cause | Fix 方向 |
|---|---|---|
| `[v2-bundle-debug] _run_v2_bundle_confirm enter: parsed_components=0` | `_apply_v3_decisions` 把 parsed_result['components'] 弄空 | 修 `_apply_v3_decisions` |
| `_run_v2_bundle_confirm enter: parsed_components=4` 但 `confirm: bundle.parsed_components=0` | `dict_to_bundle` silent skip | 修 `bundle_restore._restore_component` |
| `confirm: bundle.parsed_components=4` 但 `_confirm_service.confirm returned: written_components=0` | `_component.write()` per-row insert 全失敗（logger 沒寫 app.log） | 補 logger config / 修 insert |
| `_confirm_service.confirm returned: written_components=4` 但 DB 還是 0 row | transaction rollback 在更晚 step | 找 transaction boundary |
| 沒看到 `_run_v2_bundle_confirm enter` log | confirm_import 整個 try block 抓到 exception 沒走進 v2-bundle | 看 confirm_import line 760 try block |

- [ ] **Step 2: 寫 failing test 對應該位置**

範例（假設 root cause 是 `_apply_v3_decisions` 在 components_decisions=[] 時把 components 弄空 — **inline repro 證偽，所以這條候選低**）：

```python
# tests/test_ssp_docx_import_app_service.py 加
def test_v2_bundle_confirm_writes_components_when_decisions_empty(
    app_service, mock_confirm_service, sample_parsed_result_with_components,
):
    """FE 沒送 components_decisions / la_decisions 時，BE 應該仍寫入 docx 帶來的
    components/leveraged（mirror added × None default → use_docx semantics）。
    """
    # arrange parsed_result 帶 4 個 components + 1 個 leveraged
    job = MagicMock(parsed_result=sample_parsed_result_with_components,
                    source_type="module_frame", source_uid="mf-uid",
                    status="awaiting_review", mode="statement_only")
    # ... fixture
    # act
    app_service.confirm_import(parse_uid="job-uid", payload={
        "decisions": [], "parties_decisions": [],
        "components_decisions": [],  # 空
        "leveraged_authorizations_decisions": [],
        "source_uid": "mf-uid",
    }, user_context=mock_user_context)
    # assert
    args, _ = mock_confirm_service.confirm.call_args
    assert len(args[0]["components"]) == 4, "components shouldn't be wiped"
    assert len(args[0]["leveraged_authorizations"]) == 1
```

- [ ] **Step 3: 跑 test 確認 FAIL**

```bash
poetry run pytest tests/test_ssp_docx_import_app_service.py::test_v2_bundle_confirm_writes_components_when_decisions_empty -v
```

### Task 2.2: Fix root cause

- [ ] **Step 1: 根據 Phase 0 結論寫 fix code**（具體 code 留 Phase 0 後寫，避免猜）

- [ ] **Step 2: 跑 Task 2.1 test 確認 PASS**

- [ ] **Step 3: 跑全套 test 確認沒退化**

```bash
poetry run pytest tests/ -q
```

### Task 2.3: 拿掉 Phase 0 加的 debug log

- [ ] **Step 1: revert 或重新 edit 移除 `[v2-bundle-debug]` log entries**

```bash
grep -nE '\[v2-bundle-debug\]' app/oscal/service/ssp_docx_import_app_service.py domain/oscal/import_pipeline/confirm_service.py
# 應該沒輸出
```

- [ ] **Step 2: 確認 test 仍 pass**

```bash
poetry run pytest tests/test_ssp_docx_import_app_service.py -q
```

### Task 2.4: Commit Bug T fix

- [ ] **Step 1: stage + commit**

```bash
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be add \
  <修改的檔案們>
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be commit -m "$(cat <<'EOF'
fix(ssp-oscal-alignment): Bug T — v2-bundle confirm silent fail 0 row components/leveraged

Root cause: <根據 Phase 0 結論填>

Fix: <根據 fix code 填>

Test: tests/test_ssp_docx_import_app_service.py 加
test_v2_bundle_confirm_writes_components_when_decisions_empty。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Phase 3: Fix Bug U — FE confirm payload 漏 4 個 v3 decision keys

> `SspDocxImportPage.vue` L736-744 手刻 payload，沒 wire `components_decisions` / `leveraged_authorizations_decisions` / `inventory_items_decisions` / `system_characteristic_decision`。store 的 `buildConfirmPayload()` 已正確含這 4 個 keys 但沒被用。

**Files:**
- Modify: `compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue:617-744`

### Task 3.1: 跨 repo 必讀 FE CLAUDE.md

- [ ] **Step 1: read FE CLAUDE.md 確認 commit 規範 / 不切 branch / 改 store 要不要動 invalidate**

```bash
head -200 ~/Projects/Billows/Audit-Manager/compliance-manager-fe/CLAUDE.md
```

### Task 3.2: 改 confirm payload 補 4 個 v3 keys

- [ ] **Step 1: 在 SspDocxImportPage.vue L736 `const payload = {...}` 補 4 個 keys 從 diffStore**

```vue
// 在 L736 const payload = { 內加：
const components_decisions = Object.entries(diffStore.decisions.components || {}).map(
    ([row_uid, d]) => ({ row_uid, action: d.action ?? 'skip' })
)
const leveraged_authorizations_decisions = Object.entries(diffStore.decisions.leveraged_authorizations || {}).map(
    ([row_uid, d]) => ({ row_uid, action: d.action ?? 'skip' })
)
const inventory_items_decisions = Object.entries(diffStore.decisions.inventory_items || {}).map(
    ([row_uid, d]) => ({ row_uid, action: d.action ?? 'skip' })
)
const system_characteristic_decision = diffStore.decisions.system_characteristic?.action ?? null

const payload = {
    decisions,
    parties_decisions,
    components_decisions,
    leveraged_authorizations_decisions,
    inventory_items_decisions,
    system_characteristic_decision,
    manual_assignments: [],
    skipped_paragraph_idxs: [],
    predicted_controls_user_selection: parsedResult.value?.predicted_module_frame_controls || [],
    source_uid: effectiveTargetUid || props.targetUid || undefined,
    content_overrides: contentOverrides,
}
```

- [ ] **Step 2: FE 跑 lint / 即時看 console**

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
npm run lint -- src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
```

預期：no error

### Task 3.3: Commit Bug U fix

- [ ] **Step 1: stage + commit FE**

```bash
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe add \
  src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-fe commit -m "$(cat <<'EOF'
fix(ssp-oscal-alignment): Bug U — confirm payload 補 4 個 v3 decision keys

Root cause: SspDocxImportPage.vue L736-744 手刻 confirm payload，沒帶
components_decisions / leveraged_authorizations_decisions /
inventory_items_decisions / system_characteristic_decision。
diffStore.buildConfirmPayload 已正確含這 4 keys 但沒被使用。
BE 端 _apply_v3_decisions 對「沒給 decision」走 backfill cur path —
若 current 為空 final_list 也空 → components 永遠不寫入。

Fix: payload 內補 4 個 v3 keys 從 diffStore.decisions 收集。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Phase 4: Manual E2E verify（user 操作）

### Task 4.1: 提醒 user 重啟 BE

- [ ] **Step 1: 跟 user 說**

```
Phase 1-3 BE + FE fix 完成。請：
1. 重啟 BE（service 層改了）— 不要直接吃 cache，用 kill -9 找 :8000 listener
2. FE: cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe && npm run dev（如果還沒起）
3. 走完整流程：
   (a) 開 /module-frame/b8065ad5-e169-4df4-9bd3-5ec0d03b2e1d/template-edit 看 parties 5 個 + components 0 個（baseline）
   (b) 上 /module-frame/b8065ad5-e169-4df4-9bd3-5ec0d03b2e1d/import-docx 上傳同份 docx
   (c) STEP 2 全選「採用新值」（含 components / leveraged tab）
   (d) STEP 3 確認後按確認匯入
   (e) 回 template-edit 確認 components 出現 4 個 + leveraged 出現 1 個
   (f) 再做一次同樣動作但 STEP 2 部分 party 改選「保留現值」（mix scenario）
   (g) 確認保留現值的 party 鉤稽資料 + link 都還在
```

### Task 4.2: User verify 完回報後跑 DB SQL 確認

- [ ] **Step 1: DB 真實狀態 check**

```bash
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -A -F$'\t' -c "
SET app.is_super_admin='t';
SELECT 'comp_after' k, count(*) FROM oscal.ssp_components WHERE ssp_id IN (219, 228);
SELECT 'la_after' k, count(*) FROM oscal.ssp_leveraged_authorizations WHERE ssp_id IN (219, 228);
SELECT 'rp_mf_after' k, count(*) FROM oscal.oscal_responsible_parties WHERE context_type='module_frame' AND context_id=343;
SELECT p.id, p.name, p.user_id, p.org_unit_id FROM oscal.oscal_parties p
  JOIN oscal.oscal_responsible_parties rp ON rp.party_uuid = p.uid::text
 WHERE rp.context_type='module_frame' AND rp.context_id=343 ORDER BY p.id;
"
```

預期（fix 成功）：
- `comp_after >= 4`（兩個 shell 合計）
- `la_after >= 1`
- `rp_mf_after = 5`（不少不多）
- parties 內 user_id / org_unit_id 應保留 user 之前鉤稽的值

不符 → STOP，回 root cause Phase 0 重新 verify。

---

## Phase 5: 收尾（**等 user 明確說「收尾」/「verify 通了，收尾」才執行**）

> **per memory `feedback_wait_for_user_command_to_close.md`**：fix code commit 完只給 user 一句話 status + 手測 checklist。預先寫好的 BE docs 留 working tree 不 commit。user verify pass + 明確下令才 stage + commit 收尾文件。

### Task 5.1: design.md 加 §11.39

- [ ] **Step 1: 在 `docs/features/FR-028-2605-ssp-oscal-alignment/design.md` §11 index 加新段 + §11.39 新段內容**

涵蓋：Bug S/T/U root cause + commits + 教訓

### Task 5.2: changelog

- [ ] **Step 1: 開 `docs/changelog/2026-05-26-fix-bug-stu-docx-import-components-and-keep-current.md`**

frontmatter type=fix，modules=[oscal, ssp-docx-import, frontend]，commit=<填 BE/FE hash>

### Task 5.3: FIXED-SUMMARY

- [ ] **Step 1: 開 `docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-26-bug-stu-FIXED-SUMMARY.md`**

### Task 5.4: Memory feedback（2 條）

- [ ] **Step 1: `feedback_write_strategy_no_stale_link_cleanup.md`**

> write_strategy.write_parties 不該在 caller 沒傳進來時自作主張砍 link — caller (`_collect_parties_to_unlink`) 已明確收集要砍的，write strategy 只負責「caller 給的這批寫入」。Bug S 踩過 mf strategy 內 stale cleanup 砍 keep_current link，造成鉤稽資料消失。SspWriteStrategy 本來就沒這 cleanup（對的）。

- [ ] **Step 2: `feedback_fe_payload_use_store_builder.md`**

> FE 對 BE confirm/submit payload 要走 store 統一的 buildXxxPayload()，不要在 view 內手刻 — store 已維護完整 schema，view 手刻容易漏 key（Bug U 漏 4 個 v3 decision keys 兩個月）。新增 BE accept 的 payload key 後，先驗 store builder 有沒有，再驗 view 是否用 builder。

- [ ] **Step 3: MEMORY.md 加 2 行 index**

### Task 5.5: 對話歷史歸檔

- [ ] **Step 1: 跑 `scripts/extract_claude_sessions.py`**

```bash
python3 scripts/extract_claude_sessions.py --date 2026-05-26 --topic bug-stu-docx-import-components --auto
```

### Task 5.6: 標 ✓ FIXED 標頭（既有 handoff）

- [ ] **Step 1: 把以下檔案頂端加 `> ✓ FIXED — 2026-05-26 收尾，見 FIXED-SUMMARY`**

- `docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-r-diff-service-duplicate-entry-handoff.md`（如果本期 fix 涵蓋這些）
- `docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-pqr.md`
- `docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-r-deeper.md`

### Task 5.7: 橫向文件更新（per CLAUDE.md「回頭更新文件」step）

- [ ] **Step 1: 檢查並更新以下文件對齊本期改動**

- `docs/claude/frontend-overview.md` — FE buildConfirmPayload pattern 新增說明（若先前沒提）
- `docs/api/oscal/api-spec.md` — confirm endpoint payload 加 4 個 v3 keys 文件化
- design.md §11 index — 加 §11.39 對應 link

### Task 5.8: Commit 收尾文件（**等 user 下令**）

- [ ] **Step 1: stage 收尾文件 + commit**

```bash
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be add \
  docs/features/FR-028-2605-ssp-oscal-alignment/design.md \
  docs/changelog/2026-05-26-fix-bug-stu-*.md \
  docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-26-bug-stu-FIXED-SUMMARY.md \
  docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-r-diff-service-duplicate-entry-handoff.md \
  docs/conversation-history/2026-05-26/bug-stu-docx-import-components/ \
  docs/claude/frontend-overview.md \
  docs/api/oscal/api-spec.md

git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be commit -m "$(cat <<'EOF'
docs(ssp-oscal-alignment): Bug S/T/U 收尾橫向文件更新

- design.md §11.39 + §11 index 補 Bug S/T/U 三 bug 紀錄
- changelog: 2026-05-26-fix-bug-stu-docx-import-components-and-keep-current.md
- handoff: 2026-05-26-bug-stu-FIXED-SUMMARY.md 收尾報告
- 對話歷史歸檔 → docs/conversation-history/2026-05-26/bug-stu-docx-import-components/
- frontend-overview.md / api-spec.md 同步本期改動

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 2: 問 user 是否 push BE + FE**

```
收尾 commit 完成。BE 領先 origin <N> commits（Phase 1 + Phase 2 + Phase 5），
FE 領先 origin <M> commits（Phase 3）。要 push 哪個 repo？（per CLAUDE.md
「push 永遠要 user 明確指示」）
```

---

## 不在本期 scope

- Bug ② 對 SspWriteStrategy 等其他 strategy 的影響：本期僅 ModuleFrameWriteStrategy 有此 bug（SspWriteStrategy 已 verify 沒此 cleanup）
- FE buildConfirmPayload 對應的 store schema 重寫：本期只補 view payload 4 個 keys，store schema 維持現狀
- jedi-oscal 套件版本 bump：本期 BE 改動全在主專案，無套件異動
- BE/FE 版號 bump：本期是 bug fix，user 拍板才 bump

---

## 行為規範重要提醒（per CLAUDE.md + memory）

- **永不切 branch**（per memory `feedback_no_branch_switch`）
- **可自行 commit，不自動 push**（per memory `feedback_stage_commit_no_ask`）
- **改 BE service 層後必提醒 user 重啟 BE**（per memory `feedback_be_restart_after_service_change`）
- **跨 repo 改 FE 必 read FE CLAUDE.md**（Task 3.1）
- **DB 兩張表都 verify 才算 done**（Task 4.2 同時驗 `oscal_responsible_parties` + `ssp_components`）
- **修 bug 前先驗證 DB / response 真實狀態**（Verify Hypotheses 段）
- **plan 假設先 verify**（Verify Hypotheses A-E）
- **收尾必須等 user 下命令才做**（Phase 5）
- **服務都 user 自己起，Claude 不啟動**（Phase 4 只請 user 起，不附 `python main_socketio.py`）
- **可人工驗證就切人工**（Phase 4 不再跑 playwright，請 user 手測）
- **不要晶晶體**（per memory `feedback_no_chinglish`）
- **subagent dispatch 必加「git add 顯式檔名」**（如果 dispatch subagent，禁用 `-am`）

---

## 給 fresh session 的超短 prompt（若要換 session 接手執行）

```
請閱讀並執行 implementation plan：
docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-stu.md

3 個 bug：(S) ModuleFrameWriteStrategy 砍 keep_current link；
(T) v2-bundle confirm 寫 0 row components；(U) FE 漏 4 個 v3 decisions wire。

按 Phase 0 → 1 → 2 → 3 → 4 → 5 順序。Phase 0 必先 patch debug log 請
user 重啟 + 重做 1 次 import → pin Bug T 確切位置才能往 Phase 2 寫 fix。

注意行為規範：不切 branch / 不自動 push / 改 BE service 必提醒重啟 /
收尾等 user 下令 / 可人工驗證就切人工。
```
