一句話:本卡屬 FR-103(母卡 CM-1829),第 5 棒:api/survey/ 與 api/task_survey/ 是 jedi-survey 的接線殼(route 全在套件),但仍走 REGISTERED_APPS 掛載而非 core/plugins/。收進去讓主專案側形狀對齊。零行為變更,對外 URL 一字不動。

這一棒在做什麼(白話)

主專案接每一支 jedi 套件的方式,經過 FR-090 統一成「一支套件一個檔,放在 core/plugins/」。目前 21 支都照這個形狀,只有問卷這支沒歸位——它的接線檔還留在 api/survey/ 與 api/task_survey/,走的是另一條舊的載入路徑。

功能完全正常,形狀也對(route 在套件、主專案只組 adapters),差的只是位置。接手的人要找問卷的接線時會多找一個地方。本棒把它歸位。

為什麼確定它們是接線殼(首腦三個判準交叉查證,2026-09-16)

判準①  route 數(add_resource)      survey 0 條、task_survey 0 條
判準②  接線特徵                       兩者皆有 create_blueprint
        (build_adapters / Adapters( /
         attach( / plugin.register /
         create_blueprint)
判準③  套件依賴性質                   接線(不是「消費 service」)

對照:13 個 REGISTERED_APPS 模組中,只有這兩支 + flow_control 有接線特徵。
flow_control 是混合型(13 條自己的 route + 兩支套件 attach 上來),
且依賴尚未套件化的 cloud_integration/readmodel,不在本棒範圍(歸 FR-101)。
其餘 10 支的 jedi import 都是「消費 service」或「取用口 host_services」,
不是接線——留在 api/ 正確。

api/survey/__init__.py(174 行)檔頭自陳:「route 已上移至 jedi-survey 套件(FR-069 4.6 / P11)。本檔瘦成『組 adapters + 建 blueprint』」。

🔴 首腦一度誤判、實查後推翻的一件事

首腦原本認為「卡在 main.py 的載入契約是一個模組收一個 blueprint,而 survey 要掛兩個(設計層 survey + 作答層 task-survey),所以收不進去」。

實查後不成立。main.py:148 那條 app.register_blueprint(api_module.create_module()) 的限制只存在於 REGISTERED_APPS 這條路徑;core/plugins/ 走的是 core/app_factory.py:300-301 的 plugin.mount(app, container, plugin_host)——mount() 直接拿到 app,要掛幾個 blueprint 都可以。

現成先例:core/plugins/remote_agent.py:112 的 mount() 就是「一個 mount 掛多個東西」——它先用 mount_api=False 拿未掛載的 blueprint、自己 add_resource 加檔案下載端點、再一次掛上 app。照抄那個形狀即可。

🔴 最大的陷阱:這支套件有兩條掛載路徑,不能只搬一條

core/plugins/survey.py 已經存在,但它是 socketio 模式專用:

模式        誰掛的                                掛什麼
─────────────────────────────────────────────────────────────────────
api        api/survey/ + api/task_survey/       全部 REST route
           兩個 blueprint(本棒要搬的)

socketio   core/plugins/survey.py(已存在)       一條 route 都不掛,
           mount_api=False                        只寫 app.extensions
                                                  (供 /socket/fill-survey
                                                   的 ctx() 讀)

core/plugins/survey.py:58 有 if not host.enable_socketio: return——api 模式下它是 no-op。所以本棒要做的是讓同一個檔在兩種模式下都負責,不是新建一個檔。

🔴 socketio 模式刻意一條 REST route 都不掛(減少對外暴露面),檔頭紅字寫「不可倒退」。改完必須確認這個特性還在——RUN_MODE=socketio python main.py 起來之後,REST 端點應全部 404。

其他要保住的設計(檔頭都有紅字,不要順手「簡化」)

① adapters/config 是唯一真相,不可複製第二份。core/plugins/survey.py 檔頭明寫:「adapters/config 復用 api/survey/__init__.py,不另複製一份……複製一份的話就是第三份 adapter 知識,三份之間漂移只會在某條路徑上靜默失效」。本棒搬遷後仍要維持單一來源。

② infra/survey/adapters.py(163 行)留原地不要搬。檔頭理由:「di_containers/survey/ 與 infra/flow_control/repository/ 都直接 import 它,搬進組裝根等於那兩層反向依賴」。判準同 core/plugins/file_upload.py:只有接線用 → 搬進來;別的層也在用 → 留原地。

③ 兩個 blueprint 名不可合併。api/task_survey/__init__.py 檔頭:blueprint 名是 endpoint 名的前綴(task-survey.TaskSurveyRoute),併成一個會讓 url_for('task-survey.x') 這類引用靜默匹配到 0 條、不報錯。