本卡屬 FR-091(母卡 CM-1690),是第一批(CM-1691~1695)與第二批前三張(CM-1697~1699)驗收後留下的三件小尾巴,一張卡一起收。三件彼此獨立,各一個 commit。動兩個 repo:jedi monorepo(三件都有)與主專案 BE(只有第 ② 件一支測試)。
① 疆界守衛靜默失效。 每支套件的 tests/.../test_plugin_contract.py 有三支測試,把套件目錄下每個 .py 用語法樹解析,檢查有沒有偷 import 主專案、偷 import 白名單外的 jedi 套件、偷讀環境變數。它們用 PKG_ROOT = Path(<pkg>.plugin.__file__).resolve().parent 決定要掃哪個目錄——plugin.py 在套件根時指得對,但 FR-089/091 把 plugin.py 拆成 plugin/ 子套件後,同一行指到 plugin/ 子目錄,守衛只掃裡面 5 個檔,api/app/domain/infra 全沒掃,而測試照樣全綠。CM-1699 runner 發現並修了自己那支;首腦實查 jedi-asset(範例)與 jedi-bulletin 都中。
② 主專案一支守衛紅。 test/test_module_boundaries.py::test_upload_file_endpoint_paths_unchanged 讀 jedi_file_upload.api.__file__ 的原始碼文字找六條 URL 字面值,CM-1698 把 URL 對照表搬到 api/routing.py,它就找不到了。六條 URL 本身零變更(起 app 讀 url_map 驗過),套件側已開 FROZEN_URLS 凍結集合。
③ 第一批三支 harness 沒有 --smoke。 ai-bot、remote-agent、ai-dashboard 的 harness/dev_app.py 只能 app.run() 綁 port 起服務,首腦並行驗收時撞 port 撞了三次。標準形狀要求 --smoke(用 Flask test client 打一輪端點就結束,不綁 port)。
① jedi-asset/tests/unittest/test_plugin_contract.py:20 PKG_ROOT = Path(asset_plugin.__file__).resolve().parent
jedi-bulletin/tests/unittest/test_plugin_contract.py:29 PKG_ROOT = Path(bulletin_plugin.__file__).resolve().parent
正確寫法範本:jedi-system-core/tests/unittest/test_plugin_contract.py:35 PKG_ROOT = Path(jedi_system_core.__file__).resolve().parent
② compliance-manager-be/test/test_module_boundaries.py def test_upload_file_endpoint_paths_unchanged
套件側凍結集合:jedi-file-upload/jedi_file_upload/api/routing.py FROZEN_URLS(api/__init__.py 已 re-export)
③ jedi-ai-bot/harness/dev_app.py(__main__ 只有 create_app().run)
jedi-remote-agent/harness/dev_app.py(__main__ 有 --migrate/--seed 分支,缺 --smoke)
jedi-ai-dashboard/harness/dev_app.py:199(app.run(port=5006))
範本:jedi-system-core/harness/dev_app.py 的 smoke()+argparse --smoke;jedi-bulletin/jedi-notification 也有
import jedi_asset → Path(jedi_asset.__file__).resolve().parent)。改完必做突變:在該套件 app/service/ 任一檔加一行 from app.something import X,跑守衛要紅;改之前同樣突變不會紅(這是證明本來壞的)。還原後全綠。順手 grep 全 monorepo 其他套件的 test_plugin_contract.py 有沒有同型寫法(plugin.__file__+.parent),有就一併改並在回寫列出;首腦已查 integrity/ai-dashboard/file-upload/system-core 不中。from jedi_file_upload.api import FROZEN_URLS 後與六條期望 URL 做集合比對(注意套件 FROZEN_URLS 帶 /api/1.0 前綴還是不帶,先印出來看再比)。docstring 保留「為什麼這道守衛住在主專案」那段。主專案 venv 裡 jedi_file_upload 是 Nexus wheel,跑這支測試要 PYTHONPATH=<jedi-file-upload 目錄>。--smoke:用 app.test_client() 打該套件的代表端點(至少一條 401 不帶身分、一條 200 帶假身分),斷言失敗就 sys.exit(1),結束不綁 port。remote-agent 併進既有的 sys.argv 分支;ai-bot 與 ai-dashboard 改成 argparse。ai-bot 打 AI 端點要用假 client 頂替,不打真模型;remote-agent 需要 DB 的話沿用它 --migrate 的 compose。README 各補一行 --smoke 用法。--smoke 本身跑過即可。pytest tests/ --import-mode=importlib 全綠,且回寫貼出「守衛掃了幾個檔」的數字(改前 5、改後 30 上下)。② 主專案 PYTHONPATH=... pytest test/test_module_boundaries.py 全綠(改前 1 failed)。③ 三支 python harness/dev_app.py --smoke 各跑一次貼輸出。git commit -- <改到的路徑> 逐路徑指定;BE repo git commit -- test/test_module_boundaries.py。兩 repo 分開 commit。並行有 CM-1700 runner 在動 BE 的 core/plugins/__init__.py 與 test_module_boundaries.py——本卡只改 boundaries 那一支函式,commit 前 git diff 確認沒夾到別人的 hunk。做完 commit+回寫不等令;push 等令;branch feature/FR-075 不切。三件小尾巴都改完,各自獨立 commit,都在 feature/FR-075。