本卡屬 FR-089(套件結構整理 arc 的第一張卡,母卡待開)。目標:把 jedi-asset 整理成「之後 18 支套件都照這樣長」的範例。對外契約(9 條 URL、能力點字串、error code 字串、register() 簽名)一律不動。決策者 2026-09-12 拍板 A~E 全做。
jedi-asset 是 FR-080 把 jedi-device 與 jedi-information-system 合併出來的套件。功能正確、測試全綠,但程式碼很難讀:19 個主要檔案裡有 8 個「註解+docstring」占比超過六成,三個 model/entity 檔超過 85%;plugin.py 一個檔 416 行裝了六個 dataclass 加五個函式,讀者分不清哪些是宿主要填的、哪些是套件內部的;兩個資產型別(設備/資訊系統)是兩支不同來源硬併,命名、分頁、logger 各一套寫法。另有幾個小 bug(logger 撞名、韓文註解、死 schema)。
決策者原話:「後來寫的很多都好亂,沒有條理跟結構,物件很多都寫在一起也不知道做什麼」。這張卡整理完後,同樣的形狀要套到其餘 18 支有 plugin.py 的套件,所以本卡的產出不只是改 jedi-asset,還包括把「整理後的形狀」寫進 extraction-sop.md 讓後續照抄。
首腦核對(2026-09-12 全檔實讀):
jedi_asset/plugin.py 416 行:docstring 232 行+註解 43 行,程式碼不到 140 行。模組 docstring 70 行講合併沿革、CM-1471、D16 port 盤點——README §1/§3/§9 已有同樣內容jedi_asset/__init__.py 建 logging.getLogger("infra")、app/__init__.py 建 getLogger("app")——與主專案的 logger 名撞名,套件 log 混進主專案類別domain/entity/information_system_query_entity.py:32 有一行韓文註解api/serializers/device.py 的 DeviceMenuRequest:name/ip 宣告成 Integer,且全套件零處使用(死碼)_fill_user_names() 在 device_service.py 與 information_system_service.py 各一份,25 行逐字相同grep -rn jedi_asset --include='*.py' 排除 test/.venv),全部走 jedi_asset.plugin/.domain/.app.dto/.infra.models/.common.enum,沒有人 import api/__init__.py 內部符號,故 api 層可以自由拆套件根:/Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-asset/
jedi_asset/plugin.py 416 行,拆分主體
jedi_asset/api/__init__.py 175 行,ctx()+三個 lazy decorator+mount_routes+FROZEN_URLS 全塞 __init__
jedi_asset/api/routes/device_route.py 檔頭 20 行搬遷對照表
jedi_asset/api/routes/information_system_route.py 檔頭 30 行搬遷說明
jedi_asset/app/service/device_service.py 檔頭 wrapper 吸收表格;_fill_user_names 複本 1
jedi_asset/app/service/information_system_service.py 同上;_fill_user_names 複本 2;upsert_by_name 英文 docstring
jedi_asset/domain/entity/information_system_entity.py 末尾 15 行純註解(已刪方法的墓誌銘)
jedi_asset/domain/entity/information_system_query_entity.py:32 韓文註解
jedi_asset/domain/entity/device_entity.py 手寫 __init__ 17 參數擠一行
jedi_asset/domain/entity/device_query_entity.py 同上,to_dict 回 __dict__
jedi_asset/information_system/oscal.py 檔頭 30 行「與卡片原文的差異」
jedi_asset/infra/models/__init__.py 引用 P3.4 事故
jedi_asset/__init__.py, app/__init__.py logger 撞名
jedi_asset/common/enum/error_code.py 不是 enum,放錯目錄
jedi_asset/api/serializers/device.py DeviceMenuRequest 死碼;BooleanResponse 自帶複本
pyproject.toml 三處棒號註解
jedi_asset/migrations/001-asset-tables.sql 檔頭引用 SOP 條號/棒號/D17
主專案(compliance-manager-be):
core/app_factory.py:468 register_asset() 一行,不動
core/asset_wiring.py build_asset_adapters(),不動
docs/features/FR-069-2608-jedi-module-extraction/extraction-sop.md §2 骨架+§4 要補「plugin 分檔規格」
每組做完都跑三件事:cd jedi-asset && pytest tests/、python harness/dev_app.py --smoke(要先 docker compose -f harness/docker-compose.yml up -d 並 --migrate 一次)、主專案 pytest test/test_module_boundaries.py。前兩組零行為變更,靠 test_plugin_contract.py 的 URL 集合/能力點字串/error code 字串三組凍結測試守住。
plugin.py 留「守門三件無預設值,缺了拒絕掛載;預設放行=九條端點裸奔」+「存 provider 不存實例」;infra/models/__init__.py 留「每張 model 必在此 export,否則獨立 consumer 的 create_all 炸 NoReferencedTableError 且錯誤指向別張表」;domain/ports.py 留「守門 port 缺了要吵、資訊 port 缺了要靜,判準是缺了會不會讓不該發生的事發生」;api/__init__.py 留「三個 decorator 必須 lazy,route class 在 import 期定義時 register() 還沒跑」;common/enum/asset_enum.py 留「值照抄宿主 inventory 表既有字串,改了對不上」+ ref_type 是不同欄位那句;error_code.py 留「字串是 FE i18n key,凍結」;001.sql 留「DDL 必冪等,宿主可能在已有表的庫上跑」+「跨疆界外鍵不含」兩點。information_system_entity.py 末尾 15 行純註解整段刪。oscal.py 檔頭只留「enum 留核心不搬進來,因為它們是 ORM 欄位型別,選配模組不能被核心 model import」一句。