# A4 Phase Final SUMMARY — devices / info_systems / leveraged / 控制項 / AO 鉤稽 + SSP 寫入完整化

> **Phase**：A4（Track A：Excel 匯入第四階段）
> **狀態**：BE shipped ✅（Session A→F 共 6 session 全完成）
> **Ship 日期**：2026-05-20
> **Task arc 跨度**：2026-05-20 同日 6 session（A 設計 / B plan / C reconciler 骨架 / D reconciler 細節 / E 寫入 pipeline / F 補測試 + 收尾）
> **依賴本 phase**：A5（預覽 UI + Confirm）讀本 phase Step 7 回填的 `matched_*_id` / `match_method`

---

## 1. 為什麼做 A4（與 A4 行為差異）

A3 ship 後 Excel/docx flow 的 **parties** 已走完整 reconcile（exact / normalized / fuzzy 三階段），但 SSP Excel `confirm_import` 的 **其餘 5 個 entity**（devices / information_systems / leveraged / catalog controls / assessment objectives）跟 **SSP shell**（SystemSecurityPlan + SystemCharacteristic + SspSystemImplementation main）仍是 stub log 計數，沒實際落地。

A4 把這段補完：

- **Reconciliation 層** — 5 個新 reconciler 建在 A3 `BaseReconciliationService` 上，加 USER_SELECTED stage 處理樣板的 `matched_*` 預配對 column，並 retroactive 補 A3 PersonReconciler / OrganizationReconciler
- **Write 層** — SSP shell 建立 / 既有 SSP 解析 + 5 WriteStrategy（含 Device / InfoSystem / Leveraged 共用的 `AbstractSspImplementationItemWriteStrategy` base class）
- **App service _write_all_data** 重整為 8-step 完整 pipeline（party reconcile → SSP shell → typed dataclass → orchestrator reconcile → party write → 5 entity write → parsed_result 回填 → 統計 return）
- **A2 §15.3 整併** — superset flow 的 `include_controls` 從 A4 ControlReconciler 結果直接反查 catalog，A4 ship 後 subset profile 立刻生效
- **A3 retroactive** — `_dict_to_parties` 補 `matched_user_label` / `matched_org_unit_label` 傳遞，A3 漏掉的樣板 lookup col USER_SELECTED stage 一次補完

| Caller / 區塊 | A2 / A3 ship 後 | A4 Session F ship 後 |
|--------------|----------------|---------------------|
| `_write_all_data` superset flow | 寫 MF + parties；devices/info_systems/leveraged/controls/AO 只 stub log 計數 | 寫 MF + parties + **SSP shell** + **control_implementations** + **control_impl_objectives** + **3 種 ssp_system_implementation_items** |
| `_write_all_data` update flow | 寫既有 MF 的 parties；其他 stub | 同上 + `_resolve_existing_ssp_shell` 找既有 SSP（缺 SSP raise `GRC_412026`） |
| `import_summary` response 欄位 | `parties_written` + 5 個 `*_pending` 計數 | `parties_written` + `controls_written` / `aos_written` / `devices_written` / `info_systems_written` / `leveraged_written` + `ssp_id` / `sys_impl_main_id` |
| 5 entity reconciler | 不存在 | Device / InformationSystem / Leveraged / CatalogControl / AssessmentObjective 完整三階段（USER_SELECTED / EXACT / NORMALIZED；Leveraged + fuzzy；無 fuzzy on Device/InfoSys/Control/AO 嚴格識別符） |
| A3 parties USER_SELECTED stage | 漏處理（樣板 `matched_user_label` / `matched_org_unit_label` 沒生效） | 補齊 — 同流程 A3 reconciler 也支援 |
| UNMATCHED 行為 | 未實作 | Control / AO: log warning + skip（沒 FK 寫不進去）；Device / InfoSystem / Leveraged: 仍寫入純文字 + FK=null（保留 user data 給 A5 UI 後補配對） |

詳細 reconciler 演算法 / pipeline 步驟見 `design-A4.md` §5 / §6.1。

---

## 2. Commit 清單

### compliance-manager-be（branch `feature/ssp-import-export-phase2`）

| Commit | 內容 | Session |
|--------|------|---------|
| `1de5f51` | docs(ssp-import-export-phase2): A4 design.md | A |
| `57bcb2e` | docs(ssp-import-export-phase2): A4 implementation-plan.md | B |
| `57a9567` | feat(oscal): A4 T1 stage 0 hook + dataclass + helpers | C |
| `72ef229` | feat(oscal): A4 T2 reconciler + orchestrator skeleton + DI | C |
| `7c2b860` | feat(oscal): A4 T3 reconciler full algorithm | D |
| `8936aab` | feat(oscal): A4 T4 A3 retroactive USER_SELECTED stage | D |
| `9d7c102` | feat(oscal): A4 T5 A2 §15.3 superset include_controls 反查 | D |
| `40c4a12` | feat(oscal): A4 T6 SSP shell + GRC_412026 error code | E |
| `579eb76` | feat(oscal): A4 T7 5 WriteStrategy + base class | E |
| `78118eb` | feat(oscal): A4 T8 _write_all_data 8-step pipeline + DI 完整化 | E |
| `81073ef` | docs(ssp-import-export-phase2): A4 Session E 收尾 — design §11.1 + changelog + tracker + handoff E→F | E |
| `600f50a` | test(oscal): A4 T9 unit + integration tests 補齊 boundary case | F |
| `00731d0` | docs(ssp-import-export-phase2): A4 Session F → T11 handoff prompt（縮 scope 後）| F |
| `(本 commit)` | docs(ssp-import-export-phase2): A4 Session F / T11 收尾 — SUMMARY + tracker + T9 changelog | F |

**A4 BE 共 10 個實作 commit**（feat × 8 + test × 1 + docs E 收尾 × 1）+ 4 個 docs handoff/SUMMARY commit。

### jedi-* 套件

**不動**（design §10 鎖定範圍）。jedi-oscal 維持 path-dep（feature 整體完工才一次 bump version + 推 Nexus）。

### compliance-manager-test

**T10 cucumber 拍板跳過 — 留 follow-up F-A4-cucumber**（理由見 §5）。

---

## 3. 改動範圍

### 新建檔案

**Domain layer — reconciliation/**（5 個 + 1 個 helper）：
```
domain/oscal/service/reconciliation/
├── catalog_control_reconciler.py        # 二階段 (USER_SELECTED + EXACT/NORMALIZED on control_id)
├── assessment_objective_reconciler.py   # 走父 control 配對，reconcile_with_control_map
├── device_reconciler.py                 # 三階段 (USER_SELECTED label / EXACT IP+name / fallback)
├── information_system_reconciler.py     # 三階段 + system_owner label 反查
├── leveraged_reconciler.py              # 三階段 (USER_SELECTED party / EXACT name / FUZZY_NAME_PREFIX)
└── ssp_entity_orchestrator.py           # facade — 5 reconciler dispatch by entity type
```

**Domain layer — write_strategy/**（5 strategy + 1 base）：
```
domain/oscal/service/write_strategy/
├── base.py                                       # AbstractSspImplementationItemWriteStrategy
├── control_write_strategy.py                     # 寫 control_implementations，返 ctrl_impl_id_map
├── ao_write_strategy.py                          # 寫 control_impl_objectives，需 ctrl_impl_id_map 注入
├── device_write_strategy.py                      # kind='hardware'（繼承 base）
├── information_system_write_strategy.py          # kind='system'（繼承 base）
└── leveraged_write_strategy.py                   # kind='leveraged-authorization'（繼承 base）
```

**Tests**（17 個 A4 test 檔，~165 test）：
```
tests/test_a4_*.py
├── test_a4_parsed_entity_fixtures.py             # 共用 fixture
├── test_a4_reconciliation_helpers.py             # stage 0 dispatch / by-key 對齊
├── test_a4_reconciliation_orchestrator.py        # facade dispatch
├── test_a4_reconciliation_device.py              # 三階段 + boundary
├── test_a4_reconciliation_information_system.py  # 三階段 + system_owner
├── test_a4_reconciliation_leveraged.py           # fuzzy + suffix strip
├── test_a4_reconciliation_catalog_control.py     # 二階段
├── test_a4_reconciliation_assessment_objective.py # reconcile_with_control_map
├── test_a4_a3_retroactive.py                     # A3 retroactive PersonReconciler / OrganizationReconciler
├── test_a4_a2_integration.py                     # A2 §15.3 superset include_controls 反查
├── test_a4_ssp_shell.py                          # _create_ssp_shell / _resolve_existing_ssp_shell
├── test_a4_write_strategy_base.py                # base template method
├── test_a4_write_strategy_control.py             # 寫 control_implementations
├── test_a4_write_strategy_ao.py                  # 寫 control_impl_objectives
├── test_a4_write_strategy_device.py              # 繼承 base
├── test_a4_write_strategy_information_system.py  # 繼承 base
├── test_a4_write_strategy_leveraged.py           # 繼承 base
├── test_a4_write_all_data_pipeline.py            # 8-step pipeline + _dict_to_* + by-key 對齊
├── test_a4_boundary_cases.py                     # T9 boundary（reconciler 多 candidate / abbr 解析 / Control+AO empty list）
└── test_a4_reconcile_to_write_integration.py     # T9 真實 reconciler+strategy 整合
```

### 改動既有檔案

| 檔案 | 改動範圍 |
|------|---------|
| `common/code/grc_error_code.py` | 新增 `GRC_EXCEL_UPDATE_FLOW_NO_SSP = ("Excel update flow 失敗：對應 MF 尚未建立 SSP", "GRC_412026")` |
| `app/oscal/service/ssp_excel_import_app_service.py` | `__init__` 從 8 個 dep → 19 個（A2 8 + A3 1 + A4 phase 10 + profile_domain extra 1）；`_write_all_data` 重整為 8-step pipeline；新增 `_create_ssp_shell` / `_resolve_existing_ssp_shell` / `_resolve_catalog_id` / `_update_parsed_result_with_reconcile` / 5 個 `_dict_to_*` helper / 1 個 module-level `_iter_paired` by-key 對齊 helper |
| `di_containers/oscal/oscal_containers.py` | 加 5 reconciler Factory + 1 orchestrator Factory + 5 WriteStrategy Factory；`ssp_excel_import_app_service` Factory 19 個 wiring |
| `domain/oscal/service/reconciliation/base.py` | 加 stage 0 dispatch hook（檢查 `matched_*_id` / `matched_*_label` 直接出 USER_SELECTED） |
| `domain/oscal/service/reconciliation/person_reconciler.py` + `organization_reconciler.py` | A3 retroactive — 接 stage 0 hook，配 `_dict_to_parties` 傳 `matched_user_label` / `matched_org_unit_label` |
| `domain/oscal/parser/ssp_intermediate.py` | `ParsedParty` 加 `matched_user_label` / `matched_org_unit_label`（optional）；新增 5 個 `Parsed*` dataclass（ParsedDevice / ParsedInformationSystem / ParsedLeveraged / ParsedCatalogControl / ParsedAssessmentObjective）+ MatchMethod 擴張 (USER_SELECTED) |
| `app/oscal/service/ssp_excel_parser_service.py` | `_dict_to_parsed_parties` 補 label 傳遞 |

---

## 4. 規範文件齊全度

| 項目 | 路徑 | 狀態 |
|------|------|------|
| Design.md | `docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A4.md` | ✅ Session A ship (1de5f51；含 §11.1.1-16 共 16 條偏差紀錄) |
| Implementation plan | `docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A4.md` | ✅ Session B ship (57bcb2e；3466 行 / 11 task / 4 session) |
| Changelog（Session E 主 ship）| `docs/changelog/2026-05-20-feat-ssp-excel-import-phase2-a4-write-pipeline.md` | ✅ commit 40c4a12 + 579eb76 + 78118eb；含 7 條 Session E 偏差摘要 |
| Changelog（T9 boundary tests）| `docs/changelog/2026-05-20-tweak-a4-boundary-and-integration-tests.md` | ✅ commit 600f50a；type=tweak |
| Tracker | `docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md` | ✅ A4 row → BE shipped + 10 commits + 2 changelog |
| 對話歷史 dump | `docs/conversation-history/2026-05-20/ssp-import-export-phase2-A4/` | ⚠ **pending** — 等 user 提供 conversation export 後再 dump（不二次加工 / 原樣寫入） |
| Session handoff prompt | `docs/features/FR-011.2-2605-ssp-import-export-phase2/handoff/2026-05-20-a4-*.md` (b-to-c / c-to-d / d-to-e / e-to-f / f-t11-only) | ✅ 5 個 handoff prompt 完整保留 |

design-A4.md §11.1 已含 Session C / D / E 共 16 條偏差紀錄。Session F (T9 + T11) 沒新偏差 — T9 純補 test 行為一致，T11 純文件，§11.1 不再追加。

---

## 5. 已知 follow-up

| # | 項目 | 處理時機 |
|---|------|---------|
| **F-A4-cucumber** | **T10 cucumber regression（5 scenarios）— 拍板跳過，留 follow-up** | env 配齊那天一次性補完 |
| F-A5 | A5 phase 預覽 UI + Confirm 寫入（用本 phase Step 7 回填的 `matched_*_id` / `match_method` 讀），fuzzy 拍板路徑 | A5 phase |
| F-conv-history | 對話歷史 dump 到 `docs/conversation-history/2026-05-20/ssp-import-export-phase2-A4/`（拆 part 6 個 sub-topic：A 設計 / B plan / C reconciler / D reconciler-details / E write-pipeline / F test）| user 提供 export 後 |
| F-A4-refactor | A4 內部 refactor（如 reconciler 是否再合併 / orchestrator 是否再抽象）— 暫無觸發點，看下游 phase 是否需要 | 視 A5/B2 / 跨 entity 需求 |
| F-jedi-oscal-publish | feature 整體 ship 後 user 拍板 → bump jedi-oscal version + 推 Nexus + 主專案 pin 新版本 | Phase 2 task arc 收口時 |

### F-A4-cucumber 撰寫前置條件（env 配齊那天執行）

跨 repo work — 在 `~/Projects/Billows/Audit-Manager/compliance-manager-test/` 開 branch。

**前置條件**：
1. GitLab CI pipeline 配齊（A1/A2/A3 cucumber 都同期 partial ship / pending — 同期 4 phase 都等 env 配齊）
2. A4 fixture seed helper 配齊：device 'Web-01' / info_system 'ERP' / Party 'AWS GovCloud' / catalog 'AC-1' 等

**範圍**：新建 `features/regression/module-frame/06-ssp-excel-import-entity-match.feature` 含 5 scenarios：
- control_id 對到 catalog 內 control (exact)
- device matched_device 下拉選定 (user_selected)
- info_system name 對到 tenant entity (exact)
- leveraged provider fuzzy 對到 Party (fuzzy_name_prefix)
- AO objective_id 父 control 配到後自身配到 (exact)

step + page object 沿 A3 cucumber pattern（`feature/a3-ssp-party-match-regression` ship 在 commit `27e52fe`）。

**partial ship pattern**：env 配齊那天可一次性補完 A1 / A2 / A3 / A4 共 4 phase cucumber。

### 跳 T10 理由（2026-05-20 拍板）

- GitLab CI pipeline 配齊時程 user 不清楚 → 寫 cucumber 也只是 dead code
- A1 / A2 / A3 cucumber 都同步 partial ship / pending — 同期 4 phase 都等 env 配齊
- 留 follow-up F-A4-cucumber：env 配齊那天一次性補 4 phase 比較有效率

---

## 6. 部署 Handover

### 6.1 套件狀態

- **jedi-oscal**：維持 path-dep（`pyproject.toml` dev-path 改動不 commit；feature 完成才一次 bump version + 推 Nexus）
- **jedi-auth / jedi-common**：A4 不動 — 維持 Nexus pin
- **主專案 `pyproject.toml`**：A4 期間 dev-path 改動 working tree 仍未 commit（正常 — Phase 2 整體完工才一起還原 + commit）

### 6.2 BE 重啟需求

**A4 改動了 app service / DI Container / domain layer**（`app/oscal/service/ssp_excel_import_app_service.py` + `di_containers/oscal/oscal_containers.py` + 11 個新 domain class）— BE 沒 hot reload，下次 manual smoke 測 A4 confirm flow 前**必先重啟**：

```bash
lsof -ti:8000 | xargs kill -9
nohup poetry run python main_app.py > /dev/null 2>&1 &
```

### 6.3 DB / Migration

- A4 **無 schema 異動**（依賴 A0 / A0.1 既有結構 + A0.1 ship 的 `ssp_system_implementations` main + `ssp_system_implementation_items`）
- `oscal_parties` / `oscal_responsible_parties` schema 不動
- `system_security_plans` / `system_characteristics` / `control_implementations` / `control_impl_objectives` schema 不動

### 6.4 後續 phase 開工建議

A4 完工後：
- **A5 phase**（預覽 UI + Confirm 寫入）— 讀 A4 Step 7 回填的 `matched_*_id` / `match_method` / `match_confidence`；fuzzy 拍板攔截路徑
- **Track B**（SSP 匯出 B1-B6）— 可獨立起手，與 A5 解耦

下一個決策點：
- (a) 走 A5 phase（接續 Track A 完成 Excel 匯入全程）
- (b) 跳 Track B（SSP 匯出 B1-B6；可平行 / 換 thread）
- (c) 暫停評估其他 priorities

---

## 7. A4 task arc 規範遵守清單

- [x] 顯式 `git add <file>`，禁 `-am` / `-A`（10 個 BE feat/test commit + 4 個 docs commit 全用 explicit list）
- [x] Commit 含 `Co-Authored-By: Claude Opus 4.7 (1M context)` footer
- [x] jedi-* 不動（package source unmodified）
- [x] `pyproject.toml` dev-path 改動不 commit（working tree 持續未 commit ✅）
- [x] DDD 嚴格分層 — 11 個新 domain class 純 domain layer 不接 session；reconcile / write 串接點在 app service `@transaction` scope 內
- [x] T8 / T9 ship 後跑全套 a3+a4+a2 ssp_excel pytest（282 passed / 0 regression）
- [x] BE 重啟需求已標 §6.2
- [x] 不寫 docstring / 註解除非真有 non-obvious 的 why
- [x] design-A4.md §11.1 偏差段不空 — 16 條偏差條目 + 已對齊條目 ship
- [x] Handoff 文件統一放 `docs/features/<feature>/handoff/`（CLAUDE.md 2026-05-20 新規）
- [x] 階段性 commit 不用問 — Session F 直接 commit 不停下問
- [x] T6 / T8 偏差（plan 假設 vs 實際）逐條進 §11.1.10-16
- [x] T9 純 test commit 用 type=tweak（行為一致）

---

## 8. A4 phase 收口確認

✅ **A4 phase 完整 ship — 6 session（A→F）on schedule，T10 cucumber 拍板跳過留 follow-up F-A4-cucumber**

進入 A5 phase / Track B 條件已就緒：
- 5 個 reconciler + 5 個 WriteStrategy + SSP shell + 8-step pipeline + DI 19 dep wiring + ~165 個 a4 test 全綠
- A5 phase UI 需要的 matched_*_id / match_method / match_confidence 全部由 Step 7 回填到 parsed_result
- `import_summary` response 含 `ssp_id` / `sys_impl_main_id` / 5 個 `*_written` 計數，FE 可直接讀

A4 累計交付：
- **10 個 BE 實作 commit**（feat × 8 + test × 1 + docs E 收尾 × 1）
- **~165 個 a4 test** 全綠（含 Session E 58 + T9 補 12）
- **0 regression**（a3+a4+a2 ssp_excel 282 全綠）
- **16 條 design §11.1 偏差條目** 完整保留決策軌跡
- **2 個 changelog**（feat write-pipeline + tweak boundary tests）

---

## 附錄 — Session 切分回顧

| Session | 主題 | Commit | Plan task |
|---------|------|--------|-----------|
| A | 設計（brainstorm + design-A4.md）| 1de5f51 | design |
| B | 計畫（implementation-plan-A4.md + T0 verify）| 57bcb2e | plan + T0 |
| C | reconciler 骨架（base hook + 5 dataclass + 5 reconciler skeleton + orchestrator + DI）| 57a9567 + 72ef229 | T1 + T2 |
| D | reconciler 細節 + A3 retroactive + A2 §15.3 整併 | 7c2b860 + 8936aab + 9d7c102 | T3 + T4 + T5 |
| E | SSP shell + 5 WriteStrategy + 8-step pipeline + DI 完整化 + 收尾 docs | 40c4a12 + 579eb76 + 78118eb + 81073ef | T6 + T7 + T8 |
| F | T9 補測試 + T11 收尾（T10 跳）| 600f50a + (本 commit) | T9 + T11 (T10 follow-up) |

實際耗時 6 session 跟 design §12 預估的 6 session 完全對齊。
