日期:2026-05-19 本檔角色:A1 task arc 第二份 summary,承接 SUMMARY.md(T0-T3 partial),涵蓋 T4 + T5 + partial T7(BE smoke + changelog + tracker)= BE 端全部 狀態:A1 BE partial shipped(7-task plan 完成 5+部分 = 6/7 effective) 剩下:T6 FE 下載按鈕 + T7 完整 E2E(cucumber + user 端 BE smoke)
承接上 session T0-T3(5 commits / 52 test),本 session 完成 T4 filled mode 非控制項 6 領域 + T5 控制項與AO 父子 row + partial T7(BE smoke + changelog + tracker),共 3 commits / +1455 / -65 / 71 test 全綠。A1 BE 端到此完整 ship;剩 T6 FE 下載按鈕 + T7 完整 E2E 留後續 session。
c3de9fa)7 個 pre-flight verify 全跑完,確認:
device_container.device_domain_service.get_devices(DeviceQueryEntity())information_system_container.information_system_domain_service.get_information_systems(InformationSystemQueryEntity())oscal_container.oscal_party_domain_service.get_all(PartyQueryEntity(party_type='organization'))oscal_container.oscal_responsible_party_domain_service.get_all(ResponsiblePartyQueryEntity(context_type='module_frame', context_id=mf.id))oscal_container.system_implementation_item_domain_service.get_all(SspSystemImplementationItemQueryEntity(scope_type='module_frame', scope_id=mf.id))實作範圍:
_fetch_*_lookup helper(devices name (ip) / info_systems abbr - name / orgs party.name)_populate_filled_data() 6 領域寫 bundle:metadata_row(mf 自有欄位)/ parties_org+person(rp link → party_uuid → party 分組)/ devices(hardware + join Device for IP/OS)/ info_systems(component family + join InformationSystem for owner)/ leveraged(leveraged-authorization + party join)/ ref_docs(mf own)_build_data_sheet 加 _write_filled_rows() + _select_filled_rows() sheet→bundle attr 對應 mapmodule_frame_container 加 3 個 DependenciesContainer() slot;main containers.py device 直接傳 + oscal/info_system 用 override_providers (mirror line 175 既有 pattern)419907d)4 個 verify 跑完,確認:
profile_service.get_profile_by_uid(uid) 一次拿到 eager-loaded profile→profile_controls→catalog_control→assessments tree(mirror 既有 module_frame_template_import_service._load_profile_controls)ModuleFrameControlDefaultDomainService.get_all(...) + entity control_identifier / implementation_status / implementation_descriptionModuleFrameControlObjectiveDefaultDomainService.get_all(...) + entity (control_identifier, statement_identifier) 為複合 key實作範圍:
_build_controls_with_aos() 父子 flat row 組裝_load_existing_ctrl_map / _load_existing_obj_map 對應預填 lookup_write_filled_rows 加 SHEET_CONTROLS 分支:父 row(statement_id=None)套 CONTROL_ROW_FILL F2F2F2 灰底覆蓋整 row、子 row 維持白底46b8999)BE smoke 結果:
| Smoke | 結果 |
|---|---|
| T2-T5 全 71 test | ✅ 全綠 |
| module_frame_write_strategy regression(19 test) | ✅ 通 |
| module_frame_write_strategy_v2_parties regression(20 test) | ⚠️ 1 個 pre-existing fail(驗 T4 commit c3de9fa 也 fail,非 T5 regression) |
| Import sanity check | ✅ 通 |
| 完整 Containers instantiate | ⚠️ 撞 A0.1 follow-up #7 jedi-issue GitLab env(user 端問題) |
文件交付:
docs/changelog/2026-05-19-feat-mf-ssp-import-template-download.md(含 8 commits 鏈 + API spec + 行為差異 + 父子 row contract + TEMPLATE_VERSION SemVer + DI 解法 + 部署 handover)docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md A1 row 改 "BE partial shipped (5/7)" + 完整 commit 鏈 + ship 範圍與未完項說明46b8999 docs(ssp-import-export-phase2): A1 partial T7 — BE smoke + changelog + tracker ← 本 session
419907d feat(ssp-import-template): A1 T5 07_控制項與AO sheet — profile-scoped 父子 row + MF defaults 預填 + 6 tests ← 本 session
c3de9fa feat(ssp-import-template): A1 T4 filled mode 非控制項 sheet — 6 領域預填 + DI + 12 tests ← 本 session
f14a942 docs(ssp-import-export-phase2): A1 partial summary — T0-T3 收口 + 換 session 交接 ← 上 session
ffd5d63 feat(ssp-import-template): A1 T3 blank mode — generator 主邏輯 + lookup wiring + 30 tests ← 上 session
f68525f feat(ssp-import-template): A1 T2 generator base — sheet defs + styles + lookup + DV helpers ← 上 session
d9d54c2 feat(ssp-import-template): A1 T1 skeleton — route + app service + DI 接通 ← 上 session
f59c4ff docs(ssp-import-export-phase2): A1 T0 verify 結果回填 design.md (4 處) ← 上 session
b18db55 docs(ssp-import-export-phase2): A1 phase docs — design + implementation plan ← 上 session
8 commits 未 push — user 後續決定 push 時機。
| 範圍 | passed | 備註 |
|---|---|---|
| T2 helper base | 22 | T2 commit f68525f |
| T3 generator blank | 16 | T3 commit ffd5d63 |
| T3 app service base | 14 | T3 commit ffd5d63,T4 後擴成 19 → T5 後 24 |
| T4 generator filled (含 T5 1 個 controls) | 7 | T4 commit c3de9fa 6 → T5 commit 419907d +1 |
| T4 app service filled | 9 | T4 commit c3de9fa,含 lookup / build_metadata_row / build_parties / build_devices / build_leveraged |
| T5 app service controls | 5 | T5 commit 419907d — TestBuildControlsWithAos |
| 累計 | 71 | 0.4s 完成、全綠 |
| Task | 主題 | 狀態 | commit |
|---|---|---|---|
| T0 | Pre-flight verification(6 項) | ✅ | f59c4ff design.md 回填 |
| T1 | Skeleton — route + app service + DI 空殼 | ✅ | d9d54c2 |
| T2 | Generator base — openpyxl helper 共用層 | ✅ | f68525f |
| T3 | Blank mode 完整實作 | ✅ | ffd5d63 |
| T4 | Filled mode 非控制項 sheet(6 領域) | ✅ | c3de9fa |
| T5 | 07_控制項與AO sheet(父子 row + 預填) | ✅ | 419907d |
| T6 | FE 下載按鈕 | ❌ | 留後續 session — compliance-manager-fe repo |
| T7 | E2E + smoke + changelog | ⏳ partial | 46b8999(BE smoke + changelog + tracker 完成;剩 cucumber e2e + 完整 BE smoke) |
有效完成度:6/7 ≈ 86%(T7 BE 部分計入)
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'),跟 /template/download + /control-defaults/template/download 三條 URL 共存不衝突。
T0-T3 預留結構,T4 / T5 補實際 row data。bundle 結構在 app/module_frame/excel_template/generator.py。
8 個資料 sheet 結構固定,欄位增刪走 design.md §14 SemVer SOP。A2 parser 必須先讀 00_說明 R1 的版本字串作為相容性 anchor。
| Source | 撈法 |
|---|---|
USERS |
user_domain_service.get_users(UserQueryEntity()) → "nickname <login_name>" |
ORG_UNITS |
org_unit_domain_service.get_org_units_menu() → name |
DEVICES |
device_domain_service.get_devices(DeviceQueryEntity()) → "name (ip)" |
INFO_SYSTEMS |
is_domain_service.get_information_systems(...) → "abbreviation - name"(沒 abbr 退回 name) |
ORGS |
party_domain_service.get_all(PartyQueryEntity(party_type='organization')) → name |
REF_DOCS_MF |
ref_doc_domain_service.get_all(ModuleFrameReferenceDocumentQueryEntity(module_frame_id=...)) → title(MF own,非 tenant pool) |
statement_id=None, control_id=cc.control_id, control_name=cc.control_titlestatement_id=str(a.uid), control_id=cc.control_id(同父), objective_id=a.name("AC-1_smt.a" 短碼)control_identifier、obj_default key=(control_identifier, statement_identifier)CONTROL_ROW_FILL F2F2F2 灰底覆蓋整 row;子 row 白底statement_id 為空 → control row、為 AO uid → AO row;兩者皆帶 control_id 方便連續性判斷oscal_container 依賴 module_frame_container(containers.py:141),反向不可注入。解法(mirror line 175 既有 pattern):
# module_frame_containers.py
class ModuleFrameContainer(containers.DeclarativeContainer):
device_container = providers.DependenciesContainer()
oscal_container = providers.DependenciesContainer()
information_system_container = providers.DependenciesContainer()
...
# containers.py 後段(oscal/info_system 建立後)
module_frame_container.override_providers(
oscal_container=oscal_container,
information_system_container=information_system_container,
)device_container 早於 module_frame_container 建立,可在 instantiation 時直接傳。
| # | 項目 | 嚴重度 | 狀態 |
|---|---|---|---|
| 1 | BE boot 撞 jedi-issue GitLab env 問題 | 環境(A0.1 既有) | user 端問題;配齊後可跑完整 smoke |
| 2 | T6 FE 下載按鈕未實作 | 預期 | 留後續 session |
| 3 | T7 完整 E2E cucumber 未寫 | 預期 | 留後續 session |
| 4 | test_write_parties_refreshes_existing_party_in_place pre-existing fail |
low | 非 A1 引入(T4 commit c3de9fa 也 fail);OSCAL Party domain 改動的 stale test |
| 5 | profile → controls 雙段查詢效能(如 NIST 800-53 1000+ controls) | 第一版接受 | 反饋再優化(design.md §12 已記) |
| 6 | 控制項 sheet 父子 row 視覺優化(縮排 / outline / merge) | follow-up | 第一版用灰底區分,反饋再做(design.md §13 已記) |
| 7 | 樣板 row 上限 MAX_DATA_ROW=200 | low | 大 profile 可能截斷;warning log 已加,足夠第一版 |
| 8 | jedi-oscal Nexus 發版 | 例行 | Phase 2 整體完工再一次性 bump |
| 9 | pyproject.toml path-dep 還原 pin Nexus 版本 |
例行 | 整 Phase 2 收口時做 |
無需 migration — A1 是新 endpoint + 新 generator,不動 schema
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 &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
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
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)}')"切到 ~/Projects/Billows/Audit-Manager/compliance-manager-fe/ repo。
downloadExcelTemplate(uid, mode) 包 axios responseType: 'blob'zh_Hant_TW / en 對應按鈕 / dropdown 文字src/config/api/api.js實作 anchor 範例見 plan:docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A1.md Task 6 (line 533-577)
切到 ~/Projects/Billows/Audit-Manager/compliance-manager-test/ repo。
features/.../mf_excel_template_download.feature(cucumber + Playwright)A1 全 ship 後,Phase 2 next stop:
A1 7-task plan BE 端走完 5+部分 task(86% effective)。feature/ssp-import-export-phase2 branch 累計 8 commits 未 push 涵蓋 T0-T7 BE 全部。71 個 unit test 全綠,blank / filled 兩模式 code 層級完整可用;唯一卡點是 user 端 jedi-issue GitLab env 設定,配齊後立即可做完整 boot smoke。
A1 BE 收口。剩 T6 FE + T7 完整 E2E 留後續 session。