只盤不修——本棒沒有改動任何程式碼、沒有刪或搬任何檔案、沒有碰 pyproject.toml、沒有連 DB。
| 項目 | 內容 |
|---|---|
| 盤點對象 | test/ 全部 193 支 .py(189 支 test_*.py + 4 支輔助檔);scripts/ 中 2026-08-16 之後新增的檔 |
| 不動區 | scripts/sql/、scripts/build/(僅查呼叫端,不判去留)、scripts/notion_case.py——照 CM-1236 政策 |
| 基準 commit | 31160034,branch feature/FR-075 |
| 工具 | Python 3.11.9、pytest 9.0.3、ast 靜態 import 解析、grep、git log --follow |
| 判定紀律 | 每條工具命中都開檔核對過動態呼叫(sys.modules 別名、DI wiring、importlib)才列「確定」 |
結論數字:確定 7 條,疑似 0 條,工具誤判排除 19 條。
| # | 檔案 | 測的東西 | 該東西現況 | 判定 |
|---|---|---|---|---|
| T-1 | test/test_ssp_versioning_service.py(1,178 行、26 個 test) |
SspVersioningService |
第 1 棒刪除清單內(app/oscal/service/ssp_versioning_service.py) |
隨第 1 棒刪 |
| T-2 | test/test_clone_ssp_objectives_reference_documents.py(10 行、0 個 test) |
無——整檔只剩 docstring 說自己被 T-1 取代 | T-1 本身要刪,這份轉介也失去對象 | 隨第 1 棒刪 |
| T-3 | test/test_decision_merge.py 第 9 行 |
merge_by_decisions(活的,兩個 app service 在呼叫) |
但它 import 的 ImportDecisions 來自第 1 棒要刪的 domain/oscal/strategy/i_ssp_docx_write_strategy.py |
第 1 棒必須改這一行,不可刪整檔 |
| T-4 | test/test_oscal_stage_preconditions.py(240 行、14 個 test) |
app/flow_control/service/oscal_stage_preconditions.py(活的,DI 有 wiring) |
測試斷言的是被 FR-038 刻意移除的行為 | 獨立可刪(或重寫) |
| T-5 | test/decode_salt.py(10 行) |
無——是一段硬編碼 bcrypt hex 的解碼小工具 | 全 codebase 零引用,最後內容異動 2025-07-20 | 獨立可刪 |
| T-6 | test/test_bpmn_generator_basic_flow.py |
無——0 bytes | 空殼,2026-01-30 建立後未再動 | 獨立可刪 |
| T-7 | test/test_nist_parse.py |
無——1 byte(只有一個換行) | 空殼,2026-01-01 建立後未再動 | 獨立可刪 |
T-3 是本棒最值得注意的一條,因為它會讓第 1 棒踩坑。 test_decision_merge.py 測的 merge_by_decisions 是活函式,被 ssp_excel_import_app_service.py:646 與 ssp_docx_import_app_service.py:550 呼叫,測試本身有 13 個真實斷言且目前全綠。但它從第 1 棒要刪的檔案 import 了 ImportDecisions 這個 dataclass。第 1 棒若照清單直接刪檔,這支測試會 collect error。 可行的修法:merge_by_decisions 內部用 getattr(decisions, "decisions", None) 取值,也接受純 dict 和 list(decision_merge.py:65-77 的 _decisions_by_control 明文支援三種形態),而測試自己第 175 行就有一個「傳純 dict 也要能吃」的案例。所以把 _decisions() helper 改成回傳純 dict 即可,不需要保留那個 dataclass。這個判斷屬第 1 棒的實作決定,本棒只標出風險。
T-4 是典型的「綠著但沒在守」。 這支測試在模組層被 pytest.skip(allow_module_level=True) 擋掉,理由寫「FR-038 Wave 2A 停用,2B 重建」。我把 skip 拿掉後實跑,14 個 case 全紅,錯誤是 TypeError: check() got an unexpected keyword argument 'ap_uid'——FR-038 已把介面的參數從 ap_uid 改成 round_uid,而且四個被測 class 的 check() 現在一律 return PreconditionResult.ok()(原始碼註解明寫「暫 lenient」)。測試斷言的「未指派時要 fail、要回 unassigned 數量」這些行為,產品端已經不存在了。 被測模組本身是活的(di_containers/flow_control/flow_control_containers.py:21 有 wiring),所以刪的是測試不是模組。要不要改成對 lenient 行為的斷言,屬 FR-038 收口範圍,非本案。
T-1 的 skip 出於同一個 FR-038 2A 理由,但和 T-4 不同:它的被測模組確實在第 1 棒刪除清單上,所以測試跟著走,沒有重寫的問題。
這些是查過之後排除的,記下來免得下一棒重查:
| 查了什麼 | 結果 |
|---|---|
19 支測試 import app.detection_tools.service.*,ast 判定「模組不存在」 |
工具誤判。app/detection_tools/service/__init__.py 是 FR-069 的 shim,用 pkgutil.walk_packages 把 jedi_detection.app.service 整棵子樹遞迴掛進 sys.modules。實跑 importlib.import_module 七支代表模組全部 OK,指向套件實體檔。靜態解析看不到這種動態別名 |
4 支「只有 mock 斷言、沒有業務斷言」的檔(test_fr048_workflow_guard_revival / test_fr048_ssp_export_guard / test_fr048_job_comment_guard / test_workflow_execution_service_revert_job_bypass_gate) |
活的。粗篩的 awk 條件只數 assert x == y,漏算 pytest.raises。開檔看四支都用 pytest.raises(ForbiddenError) 驗守門真的擋住,是有牙齒的斷言 |
docstring 提到 v1 / jedi_oscal 的 3 支(test_manual_assignment_merge / test_fr032_excel_device_roundtrip / test_party_reconciliation_v2) |
活的。那些字句是在聲明「本檔零 v1 import 所以可以直接接 v2」,是說明不是依賴。實際 import 全指向現存 v2 模組 |
test/test_stage_advance_service.py 兩個 @pytest.mark.skip |
留著。skip 理由已寫明是 FR-038 刻意移除的行為、屬另棒處理,且該檔另外 25 個 case 是綠的。不是「等 X 而 X 早已發生」那種爛掉的 skip |
test/test_error_code_mirror_parity.py 的 skipif |
留著。條件是「jedi-compliance-audit 未安裝」,屬正常的環境條件守衛 |
test/fixtures/ 兩個 fixture 檔 |
都有人用。airasia_cmmc_l1_sample.docx 被 test_ap_report_parser_airasia.py:29 用;airasia_cmmc_l1_ar.xlsx 被 test_fr044_ar_adapter.py 與 test_fr044_ar_import_parse.py 用 |
test/fixtures_frozen_error_codes.py |
活的。被 test_module_boundaries.py:229 用 spec_from_file_location 動態載入 |
test/cloud_integration/ 21 支 |
全活。對應的 app/cloud_integration/ 與 infra/cloud_integration/ 都在,無一支 import 到刪除清單 |
| 第 2 棒的 17 個 dead file | 零測試牽連。用 dotted 路徑與檔案路徑兩種形式在 test/ 全掃,命中 0 |
第 3 棒的 12 支端點與 subtask_status_history 整模組 |
零測試牽連。四個 route class 名(ScheduleReportFileListRoute / SspScopedDocxImport / JobExecutionDevice / SubtaskStatusHistory)在 test/ 全無命中 |
common/code/event_code.py、common/code/local_code.py 刪除後,test_error_code_uniqueness.py 會不會少守 |
不會。該守衛走訪 common.code 全部子模組收 BaseCode 子類,實跑確認這兩支各貢獻 0 個 class(HIT 的只有 detection_tools_error_code / error_code / flow_control_error_code)。活的那支同名檔是 common/enum/event_code.py,四處在用 |
刪任何檔之前,現況是:
2252 collected, 2243 passed, 3 failed, 8 skipped
三支既有紅燈,與本案無關、不要順手修:
| 紅燈 | 原因 |
|---|---|
test_module_boundaries.py::test_upload_file_endpoint_paths_unchanged |
ImportError: cannot import name 'FROZEN_URLS' from 'jedi_file_upload.api' |
test_error_code_mirror_parity.py::test_mirror_has_same_members |
鏡像碼表差一個成員 GRC_REQUEST_BODY_TOO_LARGE |
test_license_cross_tenant_duplicate.py::test_same_tenant_historical_license_replaces |
測試的 _FakeDomain 缺 replace_current_atomic,套件端已加該方法 |
八個 skip 之中,四個是「.env 沒有 DB_SECRET 所以跳過 DB 整合測試」,兩個是 T-1/T-4 的模組層 skip,兩個是 test_stage_advance_service 的 case 層 skip。
刪完 T-1/T-2/T-4/T-5/T-6/T-7 之後的預期:collected 2252 → 2212(少掉 T-1 的 26 與 T-4 的 14),skipped 8 → 6,failed 維持 3。
git log --since=2026-08-16 --diff-filter=A --name-status -- scripts/ 列出 73 個新增檔。扣掉不動區(scripts/sql/ 26 支 migration + 1 支 backup、scripts/init/detection-profiles/ 與 framework-pdfs/ 的資料檔),需要查呼叫端的是下列可執行腳本。每一支都查了,全部有活的呼叫端,零支建議搬 archive/。
| 檔案 | 呼叫端 | 判定 |
|---|---|---|
scripts/check_env_scoped_migrations.py |
scripts/check_migration_manifest.sh:87 串接執行 |
現役 |
scripts/notion_create_case.py |
CLAUDE.md 派工鐵則、big-feature-workflow 與 security-scan-lead skill、docs/claude/notion-card-templates.md |
現役 |
scripts/gen_postman_collection.py |
docs/reference/postman/README.md 明列重產指令,產出物 endpoint-inventory.md 檔頭標「本檔由此腳本產出勿手改」 |
現役 |
scripts/deliverables/plugin_metrics.py |
docs/features/architecture-handbook/package-layers.md:156 定為插件化四指標量尺,FR-080 每棒回寫時重跑 |
現役 |
scripts/build/assert_db_current.sh |
build 管線 | 現役(不動區) |
scripts/build/build_bundle.sh |
build 管線,scripts/build/README.md 為 canonical |
現役(不動區) |
scripts/build/build_fe_image.sh |
同上 | 現役(不動區) |
scripts/build/build_init_image.sh |
同上 | 現役(不動區) |
scripts/build/i18n_probe.py |
scripts/build/smoke_release.sh:320 直接呼叫(smoke 探針⑦,CM-1419) |
現役(不動區) |
scripts/init/gen_schema_sql.sh / gen_seed_sql.sh / gen_stamp_sql.sh / init.sh / migrate.sh |
scripts/init/README.md 為 canonical,installer 與出貨基線流程依賴 |
現役 |
scripts/installer/install.sh / guidant / install.conf.example |
FR-065 落地版 installer 本體 | 現役 |
scripts/README.md 是 2026-08-16 的快照,之後新增的檔沒補進去。有兩支值得補:
| 檔案 | 情況 | 建議 |
|---|---|---|
scripts/deliverables/plugin_metrics.py |
8/16 後新增,README 的 deliverables/ 段落沒提到 |
補一列。另注意:docs/review/2026-09-11-fr080-arc-review.md:449 記載這支腳本有三個已知缺陷(清單指向已刪的 infra/asset/user_name_resolver.py、docstring 說 15 條實際 24 條、預設 monorepo 路徑指向不存在的目錄),採信它的輸出前要先修。本棒不修,建議首腦另開卡 |
scripts/deliverables/render_docx.py |
早於 8/16 就存在,但 README 的 deliverables/ 段落只列了 render_html / render_doc / render_index,漏了它 |
補一列。它是活的,test/test_deliverables_renderer.py 整支在測它 |
scripts/README.md 的分類統計也已過時(寫「現役工具 33 支」,實際根目錄現在是 24 支不變、deliverables/ 從 6 支變 8 支)。本棒不改 README,列出來供首腦決定是否開收尾小卡。
scripts/archive/ 五支的真實年紀CM-1236 政策是「只搬不刪,要不要真刪由決策者決定」。git log 直接查會全部顯示 2026-08-16(那是搬移的 commit),用 --follow 追到搬移前的原始異動才是真實年紀:
| 檔案 | 搬移前最後一次內容異動 | 分類 | 現在還能跑嗎 |
|---|---|---|---|
regenerate_reference_templates.py |
2026-05-29(其中 5/24 兩次是實質改動) | 一次性已完成 | 未驗,import 未查 |
2026-05-04-backfill-framework-version-file-uid.py |
2026-07-25(Sonar 批次清理,非功能改動;實質改動停在 2026-05-04) | 一次性已完成 | 未驗 |
backfill_2026-06-17_inject_job_execution_uid.py |
2026-06-17 | 一次性已完成 | 未驗 |
test_compute_engine_integration.py |
2026-07-25(Sonar 批次;實質改動停在 2026-02-10) | 疑似棄用 | 不能。見下 |
test_metadata_builder_integration.py |
2026-07-25(同上) | 疑似棄用 | 不能。見下 |
後兩支現在可以從「疑似棄用」升格為「確定死透」:CM-1236 當時的判定依據是「app/ai_dashboard/service/ 下已無 compute_engine.py」。本棒實查發現情況更徹底——整個 app/ai_dashboard/ 目錄已經不存在(app/ 下現存 25 個子目錄無此項),importlib.import_module('app.ai_dashboard') 直接 ModuleNotFoundError,也沒有任何 shim 把它導向套件。這兩支永遠不可能再跑起來。
建議:其餘三支一次性 backfill 有事後追溯價值,維持 archive;這兩支已無追溯價值(被測模組整個消失),可提請決策者裁真刪。本棒不刪。
test_decision_merge.py 第 9 行)——唯一會讓第 1 棒直接踩坑的一條。不處理就是 collect error,而且它測的是活功能,不能連檔刪掉。test_oscal_stage_preconditions.py)——240 行、14 個 case 假裝在守著六個 precondition 檢查,實際上被測方法全部改成無條件放行了。這種「skip 掛著、看起來有保護」的檔留越久越危險。scripts/archive/ 那兩支確定死透的)scripts/README.md(過時的統計與兩支漏收錄的腳本,列在 §2.2 供裁決)plugin_metrics.py 的三個已知缺陷(arc-review 已記載,建議另開卡)pyproject.toml