建議 model:opus/effort:medium — 跨三個既有子系統接線、背景執行緒租戶脈絡有已知陷阱、DI 漂移是靜默的。

本卡屬 FR-107(母卡 CM-1843)第 .2 棒(子需求卡 CM-1845),子任務 T-2.2:在主專案寫出三個轉接頭,讓套件的插座真的接到系統既有的儲存、任務與框架能力。

這張卡做什麼(白話)

T-2.1 在套件定了三個插座(「我需要有人幫我拉檔/找任務/查框架版本」),這張卡在主專案寫出對應的轉接頭(adapter),真的接到系統既有的能力上:

其中「掛證據進任務」那支本卡先留空(NotImplementedError),T-4.1 才實作——因為它要等 job_evidences 表加完欄位。

有一個已知陷阱必須處理:分類是在背景執行緒跑的,背景執行緒沒有 HTTP 請求的脈絡,讀「檔案要存到哪」的設定時會挑錯租戶(這是 memory 記過的坑)。所以拉檔一定要明確帶 tenant_id 進去解析,不能靠 thread-local。

動哪些檔

主專案
core/plugins/evidence_classification.py
  :97  DriveEvidenceSourceAdapter        ← 不動(舊線,已標 deprecated)
  :127 LivingSspControlCatalogAdapter    ← 不動
  :170 build_adapters()                  ← 多三個參數
  :204 _CONTAINER_ENV_KEYS               ← 🔴 從九個縮到只剩 ANTHROPIC_API_KEY
       (現在是:DB 四個、Drive 四個、ANTHROPIC_API_KEY)
  ← UploadProviderEvidenceStorageAdapter.stage_to_dir  補實作(T-1.3 留的 NotImplementedError)
  ← 新增 JobEvidenceTaskSinkAdapter
  ← 新增 OscalClassificationContextAdapter
infra/evidence_classification/   對應 adapter 落點(若需要拆檔)
di_containers/evidence_classification/evidence_classification_containers.py  ← 同步注入

要接的既有能力(都已存在,不要重寫)
infra/readmodel/oscal/ssp_control_implementation_query.py:74
    get_jobs_by_round_id(round_id, locale) → 回 control_id/ao_part_id/job_uid
app/upload_file/service/managed_file_upload_service.py:324
    upload_files_for_tenant()  ← canonical 的帶租戶脈絡路徑
app/oscal/service/ssp_control_implementation_service.py:237
    build_classifier_catalog_by_ssp_id(ssp_id)

三個轉接頭怎麼實作

🔴 DI 簽章漂移是靜默的

Factory 是 lazy 的:__init__ 的參數與 container 註冊對不上時,系統不會在啟動時炸,只在該端點被實際打到時才 500。所以本卡驗收必須實際打過新 route,不能只確認「BE 起得來」。這是本 repo 記過的坑(memory feedback_di_lazy_factory_signature_drift_silent)。