Phase 2 A1 — Task 0-3 收尾 + 換 session 交接

日期:2026-05-19 本檔角色:A1 task arc partial summary — T0-T3 完成、T4-T7 待續, 用於換 session 接 T4 之前的進度收口 前置文件:A0.1 SUMMARY (../ssp-import-export-phase2-A0.1/SUMMARY.md) 狀態A1 50% shipped(5 of 7 tasks),blank mode 完整可用


§1

一句話總結

A1 第一個 session 完成 7 task plan 中的前 4 task(T0 verify + T1 skeleton + T2 generator base + T3 blank mode),共 5 個 commits / 17 個檔 / 2850 insertions / 52 個 unit test 全綠。Blank mode 在配齊 GitLab env 的環境 boot 後可直接 curl 下載 9-sheet xlsx。


§2

本 session 完成的事

1. Phase docs(T0 前)

  • design-A1.md(14 段,含 §14 樣板演進與向後相容性 SemVer SOP)
  • implementation-plan-A1.md(7 個 task + 30+ test case 預估 + cross-repo 工作量估算)
  • 對齊 user 拍板:控制項 sheet B 模式(profile-scoped 父子 row 結構)+ 下拉用 tenant scope + 01_基本資料 sheet 只列 MF 自有欄位

2. T0 Pre-flight Verification(6 項)

# 結果 影響
T0.1 ⚠️ MF 沒 system_characteristic 鉤稽路徑 design 改寫 01 sheet 來源為 module_frames
T0.2 ✅ ProfileControlDomainService.get_all + catalog_control lookup 雙段查詢 不動主架構
T0.3 ✅ user_domain_service.get_users(UserQueryEntity()) RLS auto filter
T0.4 ⚠️ module_frame_reference_documents 是 MF own(無 tenant pool) design 改寫 ref_docs lookup source
T0.5 ✅ context_type='module_frame' 既有 pattern
T0.6 ✅ openpyxl 3.1.5 dict-style wb.defined_names['x'] = DefinedName(...)

3. T1 Skeleton(route + app service + DI 空殼)

開工前盤點發現既有 module_frame 已有兩條樣板下載 URL:

  • /template/download (舊版 MF 整體匯入範例)
  • /control-defaults/template/download (controls + AO 1-sheet 樣板)

→ A1 endpoint 命名改成 /api/1.0/module-frame/<uid>/ssp-import-template 避開衝突。

4. T2 Generator Base(4 helper + 22 tests)

  • sheet_definitions.pyColumnDef (含 enum/lookup 互斥 invariant) + SheetDef
    • LookupSource StrEnum + ALL_SHEETS tuple(51 個欄位 across 8 sheet
  • styles.py — PatternFill / Font / Alignment 常數
  • lookup_builder.pybuild_lookup_sheet() 建 hidden sheet + DefinedName
  • data_validation_builder.pybuild_enum_dv (含 255 char limit) + build_named_range_dv

5. T3 Blank Mode(generator 主邏輯 + lookup wiring + 30 tests)

  • generator.py 完整實作(9 sheet + 6 hidden lookups + DV + freeze panes + 00_說明)
  • header_i18n.py — 51 個 i18n key 的 zh_Hant_TW / en fallback dict
  • App service 撈 3 個 lookup data(users / org_units / ref_docs_mf)
  • DI 補 3 個 dep(user_domain_service / org_unit_domain_service / ref_doc_domain_service)

§3

完整 commit 鏈(A1 5 個 commits)

ffd5d63 feat(ssp-import-template): A1 T3 blank mode — generator 主邏輯 + lookup wiring + 30 tests
f68525f feat(ssp-import-template): A1 T2 generator base — sheet defs + styles + lookup + DV helpers
d9d54c2 feat(ssp-import-template): A1 T1 skeleton — route + app service + DI 接通
f59c4ff docs(ssp-import-export-phase2): A1 T0 verify 結果回填 design.md (4 處)
b18db55 docs(ssp-import-export-phase2): A1 phase docs — design + implementation plan

§4

測試結果

範圍 passed
T2: helper base (tests/test_excel_template_base.py) 22
T3: generator blank mode (tests/test_excel_template_generator_blank.py) 16
T3: app service helpers (tests/test_ssp_import_template_app_service.py) 14
累計 52 個 unit test 全綠

§5

A1 剩餘工作(T4 / T5 / T6 / T7)

Task 主題 範圍 預估
T4 Filled mode(非 07 控制項) metadata / parties / devices / info_systems / leveraged / ref_docs 預填到 sheets;補 device / info_system / oscal_party 三個 DI;補對應 lookup fetch 0.5d
T5 07_控制項與AO sheet profile-scoped 全 control 列出 + AO 子 row;MF 既有 implementation_statement 預填;父 row 灰底視覺區分 0.5d
T6 FE 下載按鈕 MF 詳細頁加 SplitButton + 下拉(blank / filled)+ axios blob download + i18n 0.5d
T7 E2E + smoke + changelog BE smoke / FE e2e cucumber / changelog (feat) / tracker update 0.5d

§6

DB / 環境狀態

  • dev DB:無需新 migration(A0.1 三表結構已 ship)
  • BE boot:撞 A0.1 follow-up #7 jedi-issue GitLab env 問題(需配 GITLAB_URL / GITLAB_API_VERSION / GITLAB_PRIVATE_TOKEN / GITHUB_TOKEN),A1 code 完成但 完整 boot smoke 留給 user 環境
  • jedi-oscal:仍 path-dep 模式(A0.1 既有設定,A1 沒動套件層)
  • pyproject.toml:A0.1 dev-only path-dep 改動仍在 working tree(不 commit)

§7

關鍵設計 anchor(接手 T4-T5 必看)

1. URL contract

GET /api/1.0/module-frame/<uid>/ssp-import-template?mode=blank|filled&locale=zh_Hant_TW
  • 走既有 module_frame Blueprint (url_prefix='/api/1.0')
  • 不另開 Blueprint
  • /control-defaults/template/download (1-sheet) 區隔

2. TemplateDataBundle 已預留 T4-T5 欄位

@dataclass
class TemplateDataBundle:
    # 基本(兩 mode 都用)
    mf_uid: str
    mf_name: str
    mode: str
    locale: str
    framework_name: str | None = None
    framework_version: str | None = None
    profile_name: str | None = None
    lookups: dict[LookupSource, list[str]] = field(default_factory=dict)

    # filled mode 才有(T4 / T5 補 row 寫入)
    metadata_row: dict[str, Any] | None = None
    parties_org: list[dict[str, Any]] = field(default_factory=list)
    parties_person: list[dict[str, Any]] = field(default_factory=list)
    devices: list[dict[str, Any]] = field(default_factory=list)
    info_systems: list[dict[str, Any]] = field(default_factory=list)
    leveraged: list[dict[str, Any]] = field(default_factory=list)
    controls_with_aos: list[dict[str, Any]] = field(default_factory=list)
    ref_docs: list[dict[str, Any]] = field(default_factory=list)

T4 / T5 只需在 generator _build_data_sheet 加 filled row 寫入(讀 bundle 對應 list), app service _populate_filled_data 撈資料填 bundle。

3. ALL_SHEETS spec frozen

8 個資料 sheet 結構固定,TEMPLATE_VERSION = "v1.0.0"。 T4 / T5 不應動 sheet_definitions.py(除非 design.md §14 SemVer bump)。

4. LookupSource 6 個全建(含空)

class LookupSource(StrEnum):
    USERS = "users"           # ✅ T3 已接
    ORG_UNITS = "org_units"   # ✅ T3 已接
    DEVICES = "devices"       # ❌ T4 補
    INFO_SYSTEMS = "info_systems"  # ❌ T4 補
    ORGS = "orgs"             # ❌ T4 補
    REF_DOCS_MF = "ref_docs_mf"    # ✅ T3 已接 (MF own, 非 tenant)

T4 補 3 個 DI 後改 _fetch_lookups() 內三個 placeholder 即可。

5. 控制項 sheet B 模式 contract(T5 必看)

  • profile-scoped 全 control 列出(不只 MF 已填的)
  • 父 row(control)statement_id 空 + control_id 填 + control_name
  • 子 row(AO)statement_id 填 AO UID + control_id 同父 + objective_id/name
  • 已填 MF 既有 implementation_statement / objective_statement 預填
  • 父 row 套 CONTROL_ROW_FILL 灰底(已在 styles.py 定義好)

§8

已知 follow-up / risk

# 項目 嚴重度
1 BE boot 撞 jedi-issue GitLab env 問題 環境(A0.1 既有)
2 DEVICES / INFO_SYSTEMS / ORGS lookup 留空 預期,T4 補
3 filled mode row 寫入未實作 預期,T4 / T5
4 FE 下載按鈕未實作 預期,T6
5 profile → controls 雙段查詢效能(如 NIST 800-53 1000+ controls) 第一版接受,反映再優化
6 控制項 sheet 父子 row 視覺優化(縮排 / outline / merge) follow-up,使用者反饋再做

§9

部署 handover

對 dev DB

無需 migration — A1 是新 endpoint + 新 generator,不動 schema

對 staging / production

  • A1 完整 ship 後(T7 後)才考慮 deploy
  • 完整 BE boot 在已配置 GitLab / GitHub env 環境執行
  • jedi-oscal 仍 path-dep,Phase 2 整體完工再一次性 bump

BE 重啟(user 在配齊 env 環境)

lsof -ti:8000 | xargs kill -9
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
set -a; source .env; set +a
nohup poetry run python main_app.py > /dev/null 2>&1 &

完整 smoke 步驟(T7 範本)

TOKEN="..."  # from dev login
MF_UID="..." # dev DB existing MF uid

# Blank mode
curl -H "Authorization: Bearer $TOKEN" \
     -H "X-Tenant-ID: 102" \
     "http://localhost:8000/api/1.0/module-frame/$MF_UID/ssp-import-template?mode=blank" \
     -o /tmp/template_blank.xlsx

# Filled mode (T4 / T5 完成後)
curl -H "Authorization: Bearer $TOKEN" \
     "http://localhost:8000/api/1.0/module-frame/$MF_UID/ssp-import-template?mode=filled" \
     -o /tmp/template_filled.xlsx

# Verify via openpyxl
poetry run python3 -c "
from openpyxl import load_workbook
for fn in ['/tmp/template_blank.xlsx', '/tmp/template_filled.xlsx']:
    wb = load_workbook(fn)
    visible = [s for s in wb.sheetnames if not s.startswith('_lookup_')]
    print(f'{fn}: total={len(wb.sheetnames)}, visible={len(visible)}')"

§10

session 規範遵守清單


§11

下一階段建議

接手順序(T4 → T5 → T6 → T7)

T4 適合單獨進(0.5d):

  • 加 3 個 DI(device / info_system / oscal_party)
  • 補 3 個 _fetch_*_lookup helper
  • _populate_filled_data 寫入 6 個非控制項 list(metadata_row / parties_org / parties_person / devices / info_systems / leveraged / ref_docs)
  • 補 generator _build_data_sheet 內 filled row 寫入
  • 補 unit test 6-8 case

T5 比 T4 複雜(0.5d):

  • profile → controls 雙段查詢
  • 父子 row flat list 組裝
  • 已填預填 + 父 row 灰底

T6 / T7 可平行(0.5d 各)

收口時機

T4 + T5 完成 = filled mode e2e 可用 = A1 BE 完整 — 可以做一次 BE smoke。 T6 + T7 完成 = A1 整體 ship — 寫正式 changelog + tracker update + 更新本 SUMMARY V2。


§12

結語

A1 7-task plan 走完前 4 task(57%),blank mode 純 code 層級已可用。剩 4 個 task 都是已 well-defined 的範圍,design + plan 都有完整 contract,新 session 接手成本低。

T0-T3 task arc 收口。Phase 2 A1 半 ship。