Handoff Prompt — A3 Session D → Session E(test 補齊 + cucumber + 收尾)

使用方式:把下方「交接 prompt 本體」整段貼到新 session 即可接手。 前置狀態:Session D(T3+T4)完成;A2 confirm flows 透過 _write_all_data 串接 reconcile;Person/Organization Reconciler 三階段(exact + normalized + fuzzy)全落地;working tree 乾淨(除 pre-existing pyproject.toml + conversation-history zip 異動)。 Session E 任務:T5 unit + integration test 補齊(matrix gap + ParsedParty fixture)+ T6 cucumber regression 5 scenarios + T7 changelog / design §11 reconciliation / final SUMMARY 收口 → 3~4 個 commit。


§1

交接 prompt 本體(從此貼到新 session)

我要進 SSP 匯入匯出 Phase 2 / A3 phase Session E — 收口(T5 test 補齊 + T6 cucumber + T7 changelog / design §11 / SUMMARY)。

前置狀態
========

Branch: feature/ssp-import-export-phase2(BE,37 commits 未 push)
- A0 / A0.1 / A1 / A2 都 shipped;A3 Session A / B / C / D 都 shipped
- Session D 累計 2 commits:
  - 854b238 feat(oscal): A3 T3 wire A2 confirm flows to reconciliation
  - 773e108 feat(oscal): A3 T4 fuzzy match algorithm
- 工具狀態:jedi-oscal 仍 path-dep;pyproject.toml dev-path 改動仍未 commit
- Test 基準:96 passed(A3 累計 57 + 既有 39)
  - 13 normalizers + 7 base + 3 facade + 15 person + 13 org + 6 A2 integration = 57 A3
  - 既有 8 party facade + 29 A2 service + 2 docx pre-existing fail(不擋 ship)

A3 Session E 是 5 session 切分中的最後站,task:
  T5 unit + integration test 補洞(matrix gap + ParsedParty asdict fixture)
     - Person / Org matrix gap edge cases ~2~4 case
     - ParsedParty 4 fixture(default / 顯式 set / asdict / JSONB value)
  T6 cucumber regression(compliance-manager-test repo)
     - 5 scenarios:4 exact/normalized + 1 fuzzy
     - 跑通可接受 — partial ship 允許(A0.1 follow-up #7 GitLab env 未配齊)
  T7 收口:
     - changelog feat type / modules: oscal
     - design-A3 §11 reconciliation 條目(5~7 條)
     - tracker README 更新 A3 row → done
     - final SUMMARY
  預估 ~1.25d(E session 整段)

開工 SOP
========

第一步:必讀文件(順序)

1. docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A3.md
   ← T5 段(line 1534~) + T6 段(line 1671~) + T7 段(line 1850~)
   ← 含 ~51 vs 實際 ~57 test 數差異說明(§11 補一條)
2. docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A3.md
   ← §8 acceptance criteria(test 數對照)
   ← §11 reconciliation(待補條目)
3. docs/features/FR-011.2-2605-ssp-import-export-phase2/handoff/2026-05-20-a3-session-C-to-D-implementation.md
   ← Session C 已識別的 plan vs reality 偏差(3 條 §11 待補)
4. domain/oscal/service/reconciliation/person_reconciler.py + organization_reconciler.py
   ← Session D 落地的 normalized + fuzzy 完整實作
5. tests/test_a3_reconciliation_person.py + tests/test_a3_reconciliation_organization.py
   ← 既有覆蓋面(檢查哪些 matrix gap 還沒寫)

讀完後給「我看完了,建議下一步 X」的簡短確認。

第二步:執行 T5 + T6 + T7

T5 — Test 補齊(先做,純 BE 主 repo)
  5.1 補 Person / Org matrix gap 2~4 case(plan §5.1 list)
     Person:
       - 全 stage 拿到 active=False → UNMATCHED(exact stage 已 cover,可省)
       - email 「a@」域名空 → UNMATCHED
     Org:
       - name 「公司」單獨 → stripped == "" → UNMATCHED(base 已 cover stripped == normalized)
       - 多層 suffix(「ACME Co. Inc.」)只 strip 第一個 trailing
       - 全空白 name → UNMATCHED
  5.2 新建 tests/test_a3_parsed_party_fields.py(plan §5.2 完整 code 已給)
     - default match_method == UNMATCHED + confidence 0
     - 顯式 set FUZZY_EMAIL_DOMAIN + 0.7
     - asdict() 序列化值(StrEnum instance)
     - JSON dump 後反序列化值(string "fuzzy_email_domain")
  5.3 跑全套 pytest tests/ -q 確認累計 ~270 BE test 全綠(既有 213 + A3 ~57 新)
  5.4 commit
     test(oscal): A3 T5 unit + integration tests

T6 — Cucumber regression(compliance-manager-test repo)
  6.1 切到 test repo(BE working tree clean 先)
       cd ~/Projects/Billows/Audit-Manager/compliance-manager-test
       git checkout -b feature/a3-ssp-party-match-regression
  6.2 寫 features/regression/module-frame/05-ssp-docx-import-party-match.feature
       5 scenarios(plan §6.2 完整 gherkin 已給)
  6.3 撰寫 step definitions + page object(如需)
  6.4 partial pass 接受(GitLab env 未配齊 → 列 T8 follow-up)
  6.5 在 test repo 內 commit(branch 跟 BE 主 repo 分開)
       test(a3): SSP docx party match regression scenarios

T7 — 收口(回 BE 主 repo)
  7.1 docs/changelog/2026-05-XX-feat-party-matcher-shared.md
       frontmatter type: feat / modules: [oscal] / breaking: false
       commit: 854b238 + 773e108 + 本次 T5 commit
  7.2 design-A3.md §11 reconciliation 補條目(5~7 條最少):
       1. T1.7 plan vs reality — __init__.py 只 export 2 個(circular import)
       2. T2.1 plan vs reality — 既有 8 test fixture 必須改 inject _make_facade()
       3. import path entities 複數 vs plan code snippet 單數
       4. T3 plan vs reality — reconcile 插在 _write_all_data 而非 _confirm_*_flow
          (DRY + call chain 自動受惠)
       5. test 數 ~57 vs design §8 ~38 差異(含 normalizers helper 13 個)
       6. fuzzy stage candidate cache exception → return [] 靜默路徑
          vs docx parse-time logger.warning swallow 行為差異
       7. A3 期間 A2 fuzzy 自動寫入(A5 改 user 拍板,已列 F2)
  7.3 docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md tracker A3 row → done
  7.4 docs/features/FR-011.2-2605-ssp-import-export-phase2/handoff/2026-05-XX-a3-SUMMARY.md
       含 commits 清單 / 改動範圍 / 行為差異 / 規範文件清單 / 已知 follow-up
       / 部署 handover
  7.5 commit
     docs(ssp-import-export-phase2): A3 收尾 — changelog + design §11 + SUMMARY

第三步:BE smoke + 提醒 user

T5 commit 後不需 BE smoke(純 test 補洞);
T7 commit 後也不需 BE smoke(純文件);
**但 Session D 的 T3 改動了 app service 層** — 若 user 還沒重啟 BE 跑過 manual
測 reconcile 串接,必再次提醒:
  lsof -ti:8000 | xargs kill -9

第四步:A3 完工確認 + 換 phase

A3 ship 後:
  - tracker README 更新到 A4 row 為 next
  - 若 user 想直接接 A4(5 個新 reconciler)→ 走 A4 brainstorm SOP

A3 Session A / B / C / D 已產出(參考)
=====================================

- design-A3.md(13 章 / ~670 行)+ implementation-plan-A3.md(~2,045 行)
- handoff prompts × 4:brainstorm-kickoff / A→B (writing-plans) / B→C / C→D / D→E(本 handoff)
- Session B / C SUMMARY + Session D SUMMARY(本 handoff 配對)
- tracker README A3 row 等 Session E 收口才一次更新

Session D 已落地(可直接 import):
  app/oscal/service/ssp_excel_import_app_service.py
    _write_all_data — 補 self._reconciliation.reconcile(all_parties, tenant_id=...)
                       在 write_parties 前;try/except logger.warning swallow
                       (沿用 docx flow line 258-264 既有 pattern)
  domain/oscal/service/reconciliation/person_reconciler.py
    _try_normalized_match — _strip_plus_alias + 二次 query exact
    _try_fuzzy_match — email domain + nickname/login_name + cache + is_active filter
  domain/oscal/service/reconciliation/organization_reconciler.py
    _try_normalized_match — _normalize_name(全形→半形 / collapse)+ 二次 query
    _try_fuzzy_match — _strip_org_suffix(雙邊 normalize + strip 後比對)+ cache
  tests/test_a3_reconciliation_a2_integration.py — 6 case 全綠
  tests/test_a3_reconciliation_person.py — 累計 15 case 全綠
  tests/test_a3_reconciliation_organization.py — 累計 13 case 全綠

Session D 撞到 / 需注意的事
==========================

1. **Plan T3.2/3.3 vs reality**:plan 寫「插入 _confirm_superset_flow + _confirm_update_flow」,
   實際結構兩 flow 都呼叫 _write_all_data — 插一處(_write_all_data)即可 DRY 受惠。
   test 仍透過 confirm_import / _confirm_*_flow 進入,reconcile.call_count == 1 assertion 成立。
   → Session E T7.2 design §11 reconciliation 補一條(第 4 條)

2. **Plan T3.4 test fixture 簽章 vs 實際 signature**:plan 寫 `decisions=[], overrides={}`
   參數,實際 `_confirm_superset_flow(job, parsed_result, payload, user_context)` —
   test 改用 `confirm_import("u", {}, user_context)` 走完整 path 比較自然,
   而非直接 call private method。

3. **既有 test_ssp_excel_import_app_service.py fixture 沒注入 reconciliation**:
   既有 29 test 不需改動 — `party_reconciliation_service` 是 optional default None,
   `_write_all_data` 內 `if self._reconciliation is not None` 守門保住舊行為。
   新 test 自己 inject mock_reconciliation。
   → Session E T7.2 design §11 補一條(解釋 backward-compat 路徑)

4. **既有 docx test 2 個 pre-existing fail 沿用 Session C 標記**(與 A3 無關):
   - test_create_mode_get_parse_result_all_controls_added
   - test_get_parse_result_party_added_when_ssp_has_no_parties
   - follow-up F-T1-pre 不擋 ship

5. **.env JSON parse 問題**(Session C 開工撞,Session D 未再撞):
   - line 3 JWT_SECRET={ ... } 沒 quote
   - Session D 跑 pytest 用 `set -a; source .env 2>/dev/null; set +a` 略過 source 錯誤
   - 標 follow-up F-env

鐵律(沿用 A0.1 + A1 + A2 + Session A + B + C + D 全部)
======================================================

1. 顯式 git add <file>,禁 -am / -A — commit 前 git status --short 確認
2. jedi-oscal 維持 path-dep,不 bump 版本不推 Nexus(Phase 2 整體完工才 bump)
3. pyproject.toml dev-path 改動不該 commit
4. BE 重啟必 lsof -ti:8000 | xargs kill -9
5. 改 BE service code 後必提醒 user 重啟 — BE 沒 hot reload
6. DDD 嚴格分層 — Route 不碰 DB,App Service @transaction,純函式 helper / matcher 不接 DB
7. 不寫 docstring / 註解除非真有 non-obvious 的 why
   - A3 例外:BaseReconciliationService 4 hook + _get_or_load_candidates exception 行為兩處屬「non-obvious why」
8. Test 用 mock 包 logger 避開 jedi DBLogHandler 撞 SessionLocal=None;@transaction 用 patch_session_scope
9. plan 階段假設不準確時主動修 design.md (§11 reconciliation Session E 一次補)
10. 跨 repo 切換時提醒 user:cucumber 在 compliance-manager-test repo,commit / branch 分開
11. 改 docx parser 既有行為 → cucumber regression 必跑(Session E T6)
12. 重大決策 → docs/analysis/YYYY-MM-DD-<topic>.md
13. 重型 bug / 跨域問題 → docs/issues/pending/YYYY-MM-DD-<title>.md
14. Handoff 文件統一放 docs/features/<feature>/handoff/
15. brainstorm dump 留 Session E 收口才一次 dump
16. 階段性 commit 不用問
17. Subagent dispatch prompt 必加「git add 顯式檔名,禁用 -am」

A3 phase 預估規模(剩餘)
========================

| Session | 範圍 | Task | 預估 |
|---------|------|------|------|
| E(self)| Test 補齊 + cucumber + 收尾 | T5 + T6 + T7 | ~1.25d |

剩餘總計:~1.25d → A3 完工

工具預期
========

- 直接照 plan-A3 內 bite-sized step(TDD:write failing test → run FAIL → implement → run PASS → commit)
- T5 commit 後跑全套 BE pytest(pytest tests/ -q)確認 ~270 累計
- T6 在 test repo(跨 repo 切換)
- T7 純文件 + tracker → 結束 A3
- 不需 dispatch subagent — Session E 工作量在主 session 推得動

謝謝。

§2

收口清單(Session D 端)

本 handoff prompt + Session D SUMMARY commit 後,新 session 接手即可:

  1. 讀 5 個必讀文件(plan-A3 / design-A3 / Session C→D handoff / person_reconciler / org_reconciler + 對應 test)
  2. 執行 T5 + T6 + T7(plan 內含完整 code snippet)
  3. T7 commit 後 A3 完工,可進 A4 phase
§3

Session D 累計 commits(feature/ssp-import-export-phase2)

854b238  feat(oscal): A3 T3 wire A2 confirm flows to reconciliation
773e108  feat(oscal): A3 T4 fuzzy match algorithm

外加本 handoff prompt + Session D SUMMARY commit。

§4

Session D 規範遵守