本卡屬 FR-111(母卡見建卡後補號),第 4 棒:設定與插件殼:分類設定 CRUD/模型碼表/plugin 契約/守門殼(jedi-evidence-classification,16 檔 1,850 行)。只掃不修。
「AI 分類設定」(哪家供應商、哪個型號、思考深度、AI 人設與提示詞)的建改刪查與守門、可選模型清單、以及套件怎麼掛進宿主(plugin 契約、接線檢查、路由表、port 介面)。
設定管所有專案的 AI 行為。守門是「能改系統設定的人」(CM-1867 從平台管理員放寬到租戶管理員),RLS 刻意讓每個租戶讀得到 ROOT(原廠)那一列。金鑰解析鏈「租戶→ROOT 原廠鑰→環境變數」的本體在主專案側,本棒看套件這一半:套件有沒有把鑰或「誰有鑰」露出去。插件殼是所有端點的認證來源,缺零件要拒絕啟動而不是靜默放行。
classification_profile_service.py:221-253):_require_settings_manager 走 can_manage_settings()(主專案 adapter core/plugins/evidence_classification.py:123-147,本棒只看套件呼叫了沒)。list_profiles(:142)/available_models(:119)/resolve(:84)三支讀取掛不掛守門——任何登入者能不能讀到全租戶的 AI 人設與提示詞(那是客戶的稽核方法論)。_require_own_profile(:243)改 ROOT 那列回 403——租戶管理員改不到 ROOT 確認。available_models :119-140、llm_models.py):configured_providers(tenant_id) 回哪幾家有設鑰、回給前端當可選清單——等於告訴任何能打這支的人「這個租戶/ROOT 有 OpenAI 鑰」;鑰本身不會回,但確認回應裡沒有 is_set 以外的欄位(base_url?)。resolve 的四層鏈(租戶專屬框架版本→租戶通用→ROOT 框架版本→ROOT 通用,:84-116)落到 ROOT 時,分類會用 ROOT 的原廠鑰跑——CM-1867 裁 D13「鎖 ROOT」是鎖改不鎖用,記清楚這是設計。_validated :266-303、classification_profile_schema.py):payload 哪些欄位放行、persona/prompt 長度上限(提示詞會進容器 prompt.json 再進 AI system block——E1 ⑤ 的注入源之一)、provider/model/effort 有沒有比對碼表(llm_models.py spec_for/models_for_providers)——E1 ③ 的白名單是第二道,第一道在這。有沒有 apply=False(FR-109 V1 帶出的全套件盤點項,grep -rn 'apply=False' 本套件目前 0 命中,記在報告)。migrations/005-classification-profiles.sql:19、:105-175):SELECT policy 額外放行 tenant_id = 1——ROOT 那列的所有欄位(含人設全文)對所有租戶可讀;INSERT/UPDATE/DELETE 有沒有跟著放行(不該)。DEV 唯讀實查已確認該表 RLS 開、4 條 policy。find_generic/find_for_framework_version(classification_profile_repository_impl.py:38-52)的 tenant_ids.in_(...) 帶了哪些 id。plugin/__init__.py:98 register、assembly.py:19-47 _assert_wiring+_guarded、contract.py:81-138 EvidenceClassificationAdapters):缺哪些 adapter 會拒絕掛載、缺哪些會靜默變 no-op(platform_admin_guard 缺→_guard() 拋 RuntimeError 是對的;role_guard 缺呢、auth_required 缺呢);_guarded 用動態子類套認證——mount_routes(api/routing.py:6-153)每一支 add_resource 都經 R() 包過嗎(首腦數:舊線 :40-56 三支、批次線 :65-138 十幾支、設定線 :147-153 三支,逐支對)。EvidenceClassificationConfig.container_env(contract.py:46-66)是金鑰進套件的入口——型別是 dict、有沒有被 log。domain/ports.py):IProjectRoleGuard(:63-82)三支 bool 方法——回 False 與拋例外的差別、is_any_project_manager 的語意(租戶內任一);IEvidenceStorage.stage_to_dir(:238)的 file_uids 有沒有要求屬於該租戶(實作在主專案 core/plugins/evidence_classification.py:331,檔頭自陳走 get_upload_file 而不是 per-tenant——記下來給主專案接線棒);IAiProviderConfig.container_env(:104-116)文件有沒有寫「只給一把」。llm_models.py):spec_for(provider, model) 查不到時退回什麼(f8fcde4 剛改「不再硬退 anthropic」)、supports_vision/effort 三檔對照;純資料但決定 E1 ③ 白名單前面那一道。本套件從未掃過。 分類設定是 FR-107 T-5.x/CM-1864(建表+四層解析鏈)、CM-1867(守門放寬到租戶管理員+清單保留通用那幾筆)、CM-1885(模型碼表換代+思考深度三檔)、CM-1888(型號反查供應商)連做的,scope 對到 HEAD 955e409。金鑰解析鏈本體 app/system_config/service/ai_provider_key_resolver.py(199 行)與 adapter core/plugins/evidence_classification.py:149-203 在主專案,不在本棒——本棒答「套件有沒有把鑰露出去」,答不了「解析鏈守不守」,那是主專案接線棒的事(FR README 已註明兩支要同棒)。插件殼形狀與 jedi-survey(FR-109 V1 ⑥,正面案例)同一代,可對照。
第一步:驗 scope 檔數與行數,對上 16 檔/約 1,850 行才啟動(scope 以套件 HEAD 955e409 為準;git log -1 --format=%h 不是它就先回報,不自行改 scope):
cd /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-evidence-classification && git ls-files -- jedi_evidence_classification/app/service/classification_profile_service.py jedi_evidence_classification/api/routes/classification_profile_route.py jedi_evidence_classification/api/schemas/classification_profile_schema.py jedi_evidence_classification/domain/llm_models.py jedi_evidence_classification/domain/service/classification_profile_domain_service.py jedi_evidence_classification/infra/repository/classification_profile_repository_impl.py jedi_evidence_classification/domain/entity/classification_profile_query_entity.py jedi_evidence_classification/migrations/005-classification-profiles.sql jedi_evidence_classification/plugin/__init__.py jedi_evidence_classification/plugin/assembly.py jedi_evidence_classification/plugin/contract.py jedi_evidence_classification/plugin/runtime.py jedi_evidence_classification/plugin/migrations.py jedi_evidence_classification/api/guards.py jedi_evidence_classification/api/routing.py jedi_evidence_classification/domain/ports.py | wc -l # 要 = 16
cat jedi_evidence_classification/app/service/classification_profile_service.py jedi_evidence_classification/api/routes/classification_profile_route.py jedi_evidence_classification/api/schemas/classification_profile_schema.py jedi_evidence_classification/domain/llm_models.py jedi_evidence_classification/domain/service/classification_profile_domain_service.py jedi_evidence_classification/infra/repository/classification_profile_repository_impl.py jedi_evidence_classification/domain/entity/classification_profile_query_entity.py jedi_evidence_classification/migrations/005-classification-profiles.sql jedi_evidence_classification/plugin/__init__.py jedi_evidence_classification/plugin/assembly.py jedi_evidence_classification/plugin/contract.py jedi_evidence_classification/plugin/runtime.py jedi_evidence_classification/plugin/migrations.py jedi_evidence_classification/api/guards.py jedi_evidence_classification/api/routing.py jedi_evidence_classification/domain/ports.py | wc -l # 要 ≈ 1850
第二步:把啟動指令交給決策者(你不能自己啟動)。/claude-security 這個 skill 帶 disable-model-invocation: true,模型用 Skill tool 叫會被擋;也不可以自己叫 Workflow 或自己派研究員/verifier 代替它——面板票數是工具算的、報告的驗證章蓋在那個數字上。這是刻意設計不是故障,撞到不要 debug、不要找繞路。
檔數對上後停下回報:說「檔數 16 已對上,可啟動」,並把下面兩行原樣附在回報裡交還給決策者,由決策者在本 session 親手打(單次送出、第二行不能省、每個新 session 都要重打):