FR-069 收整G(CM-1508):四類重佈局提案——現況 → 目標對照

  • 日期:2026-09-02
  • 性質:佈局設計稿(第一段產出,未經 user 點頭不搬任何檔)
  • canonical:architecture-handbook modularization-roadmap.md §「產品本體最後剩什麼——四類」
  • branch:feature/FR-069.5

§1

30 秒版結論

盤完全樹後的誠實判斷:四類實體在前面 A–F 各棒之後已大致就位——櫃檯有 infra/readmodel/(D 棒剛分完子資料夾)+app/readmodel/、接線盤散件已有「infra/<模組>/adapters.py/app/<模組>/adapter/」慣例落點、組裝根集中在 core/+config/、業務加工就是 app/<模組>/。不需要也不應該造新的頂層目錄樹(理由見下節硬約束)。

本棒實際要做的是三小批歸位+清殘:①刪四個零引用空殼目錄;②detection_execution 三層 shim 收尾(C 棒同款手法的殘留);③櫃檯 app 層歸位一支(auditor_dashboard_service)。加上④文件同步。搬動總量約 10 檔內,對外契約零觸碰。

若 user 期望的是更大規模的「砍掉重擺」,那個方向我明確反對,理由寫在下面「為什麼不動頂層」節——請看完再裁。


§2

硬約束(守衛與契約焊死的面,實查確認)

焊死的東西 誰焊的 含義
頂層八個 package 名 api / app / domain / infra / di_containers / common / core / config test_module_boundaries.py 至少 5 處以此清單迭代掃描(死名守衛、反向 import 守衛等) 改名/刪除任一個會讓多條守衛炸;新增頂層 package 則會落在守衛掃描範圍外=盲區(arc-review 剛抓過「掃描根縮水」這病,不能再造一個)
core/app_factory.py 實體路徑 test_iam_wiring.py:28 直接 read_text 這個路徑 app_factory 不可搬不可改名
di_containers/auth/auth_containers.py 路徑 test_iam_wiring.py:120 同款 read_text 不可搬
common/authz/ 六支殼 test_authz_subject_domain_files_are_shims_only 等逐檔斷言 CM-1506 保留名單,不碰
common/code/flow_control_error_code.py test_grc_error_code_string_values_frozen(讀實體路徑) 不可搬
api/<name>/__init__.py 的 create_module()+目錄名=REGISTERED_APPS 字串 main.py:142 importlib.import_module(f"api.{name}") api 下 27 個模組目錄名是載入契約,不可改名
infra/readmodel/ 結構 CM-1507(D 棒)剛照 README 四規格分完子資料夾 已是目標態,不動
對外 URL/error code/blueprint 名 D5 凍結面(231 error code/423 URL) 每批搬完 dump url_map 比對
§3

為什麼不動頂層(DDD 四層 vs 手冊四類的關係)

手冊的「四類」是功能分類(這段程式碼為什麼留在產品本體),DDD 的 api/app/domain/infra 是技術分層(HTTP 邊界/編排/領域/資料存取)。兩者是正交的座標軸,不是競爭關係:櫃檯自己就跨 app+infra 兩層(規格②的三層紀律)、接線盤在 common/app/infra 都有正當落點(各自檔頭有 SOP §5 落點論證)、組裝根住 core+config、業務加工佔 api/app/domain/infra 四層。把四類升級成頂層目錄=拆掉 DDD 分層,會同時砸掉守衛掃描面、main.py 載入契約、與全專案 import 慣例,換來的只是「目錄名跟手冊詞彙對得上」。四類的正確表達方式是手冊的實例路徑清單+各落點的 README/docstring(現在就是這樣),不是目錄樹。


§4

現況 → 目標對照表(全頂層盤點)

Python 模組層

目錄 歸類 目標 動作
infra/readmodel/(tasks/audit/oscal/detection 四子夾+README 四規格) ① 櫃檯 現況即目標(CM-1507 剛落) 不動
app/readmodel/service/(my_jobs_app_service) ① 櫃檯(app query service 層,規格②落點) 收齊櫃檯的 app 層薄殼 批3:app/flow_control/service/auditor_dashboard_service.py 搬入(它的 SQL 早在 infra/readmodel/audit/auditor_dashboard_query.py,app 殼卻還留在 flow_control;引用僅 2 處:di_containers/flow_control/flow_control_containers.py:99+api/flow_control/__init__.py 經 DI 取用不 import)
common/iam_ports.py、common/integrity/ ② 接線盤(port 宣告/完整性 adapter) 手冊實例路徑,位置正確 不動
app/flow_control/adapter/、app/license/adapter/、app/remote_agent/adapter/ ② 接線盤 手冊實例路徑 不動
infra/<模組>/adapters.py 群(survey/detection_tools/evidence_classification)、infra/participant/user_directory_adapter.py、infra/upload_file/remote_agent_adapter.py、infra/remote_agent/recon_query_adapter.py、infra/ai/redis_chat_history_store.py、infra/mail/smtp/、infra/auth/root_admin_reader.py、infra/information_system/jedi_auth_user_lookup.py ② 接線盤(各語境的 port 實作) 「接線住在它服務的語境目錄」是既定慣例,集中會斷語境 不動
infra/{notification,system_menu,issue,api_log}/*_plugin_wiring.py 四支 ②/③ 之間(插件接線,被 core/app_factory.py 呼叫) 檔頭已有 SOP §5 落點論證(core 需要的不可放 api,故落 infra) 不動——搬 core/ 會集中組裝根但斷「語境優先」慣例,且四支檔頭論證是 P3 各棒審過的
core/(app_factory/iam_wiring/upload_file_wiring/scheduler/extensions) ③ 組裝根 手冊實例路徑;app_factory 路徑被測試焊死 不動
config/(app_modules/di_modules/config/socketio_namespaces) ③ 組裝根(裝哪些積木+設定值) 手冊實例路徑 不動
di_containers/(32 個子夾+containers.py) ③ 組裝根(DI 組裝切片) auth/auth_containers.py 被測試焊死;其餘照模組切片 不動
app/<業務模組>/(oscal/module_frame/flow_control/cloud_integration/project_summary_report/auth/system_config/associations/participant/project 等) ④ 業務加工 手冊實例路徑 不動
domain/(10 個業務子夾) ④ 業務加工(domain 層) 抽剩的都是本產品專屬 不動
api/(27 個模組目錄) ④ 業務加工(HTTP 邊界)+插件接線殼(ai/survey/task_survey) 目錄名=載入契約 不動
common/(authz/code/dto/enum/middleware/util…) 共用工具(四類之外的橫切層,被守衛禁止反向 import) 現況即目標 不動

殘渣(本棒要清的)

目錄 現況 動作
app/issue/、app/log/、app/system_menu/ 只剩一支註解式 __init__.py,全樹 0 引用(實查 grep);對應功能已上移 jedi-issue/jedi-log/jedi-system-menu(P3.5) 批1:刪除
app/detection_execution/(含空的 service/) 只剩空 __init__.py,0 引用;功能已進 jedi-detection(4.4) 批1:刪除
domain/detection_execution/、infra/detection_execution/ 整棵掛回 jedi_detection 的 shim(4.4/CM-1489 留下,頭注寫「等 CM-1490 收口後清」——CM-1490 已收);引用僅 4 檔:di_containers/detection_execution/detection_execution_containers.py、di_containers/detection_tools/detection_orchestration_containers.py、test/test_detection_execution_delete_and_timeout.py、test/test_detection_execution_domain_service.py 批2:caller 改直接 import 套件 → 刪 shim(C 棒 CM-1506 同款手法,屬其清退範式的漏網殘留)

非 Python 頂層目錄(皆不動)

bin/(報表下載器)、content/(detection profiles 資料)、docker/、examples/(範例碼)、file/(runtime 上傳落地)、pki/(dev 憑證)、reports/(測試報告輸出)、resources/、static/——部署資產與 runtime 產物,不在四類語彙內,重擺無收益有風險(部署腳本/nginx/.gitignore 都指著現路徑)。


§5

執行計畫(第二段,等點頭後)

照 canonical 批次法,每批獨立 commit 可獨立回滾,每批後:守衛 80 綠+url_map 423 條零差異+python main.py 起得來。

批 內容 風險
批1 刪 4 個零引用空殼目錄 零(0 refs 已實查)
批2 detection_execution shim 收尾:4 個 caller 檔改 import → 刪 domain/…+infra/… 兩棵 shim 低(shim 本身就是 1:1 轉發;跑 2 支 detection test 檔驗證)
批3 auditor_dashboard_service.py → app/readmodel/service/,改 1 個 DI import 低(app 殼 6 行類別;dump url_map 驗 /grc/audits/my/list 不漂移)
批4 文件同步:CLAUDE.md 目錄表、docs/claude/architecture-details.md、handbook「主專案剩什麼」節實例路徑補 app/readmodel/、infra/readmodel/README.md 成員表 零
收口 全量 pytest 對照 2149 基準+url_map 零差異+實打 3–5 條核心 API+8000/8002 兩模式起服務+190 直跑驗證(卡片要求) —
§6

給 user 的裁決點

  1. 接受「小幅歸位」的定調嗎?——四類已就位是 A–F 各棒累積的結果,本棒把最後殘渣清掉+文件固化,而不是大搬遷。
  2. 批2(detection shim 收尾)要不要做?——嚴格說屬 C 棒 shim 清退範疇的殘留,做了佈局才算乾淨;不做則列 backlog 開卡。
  3. 批3(auditor_dashboard 歸位)要不要做?——唯一一件「檔案住錯地方」的實搬;不搬則櫃檯 app 層繼續一支在 readmodel、一支在 flow_control。
  4. 有沒有你心中想動、但本提案列「不動」的目錄?——特別是 plugin_wiring 四支(我判維持 infra)與非 Python 頂層目錄。