第 3 批:刪除沒人用的功能(8 件+補 M13 第 10 條 → 5 張卡)

§1

這批一句話

這批不是修漏洞,是把沒人用、卻可能被誤接上去變成漏洞的東西直接拆掉——幾支「接好但零呼叫」的任務相關功能、一支混進出貨套件的開發小工具、一個沒人讀的死開關、一個全站成員名冊入口、一個兩個入口都進不去的快速設定畫面、外加一份主專案自己的快取連線複製程式碼。可以現在就開;跟其他批沒有依賴。

本棒(開卡前座標補查)改了三件事,派工前要知道:

  1. #9/#66/#67(舊版證據分類三件)移出本批 — 由既有卡 CM-1849(FR-107.6 舊線退場)處理,原 N-1 卡只剩 M13 快取複製品那半。
  2. #121(jedi-issue 全站成員名冊)的「零引用」只有一半成立 — 網址入口確實零呼叫,但資料表與 model 被意見回饋的指派負責人路徑實際用著。這是唯一一件「計畫說零引用、實際有引用」的,已升為 D-b3-1 等決策者裁(見卡 N-4 與文末)。
  3. #72「三支腳本」實數是五支(這條在第 4 批,順帶記)。 其餘 #68/#69/#116/#117/#119/#120 六件的零引用查證七面向全部跑過、全部成立,座標已寫進各卡,runner 拿到可直接開工。
§2

卡片清單

卡 卡名(做什麼,不寫代號) repo/套件 涵蓋 SUMMARY # 建議 model/effort 序列組
N-1 BE 側:刪掉主專案自己那份快取連線複製品、改接套件 BE 主專案 M13 第 10 條(#9/#66/#67 已移除,由 CM-1849 處理) sonnet/medium A
N-2 jedi-task-platform:拆掉三組零呼叫的任務管理功能 jedi-task-platform 套件 #68 #69 #120 sonnet/medium B
N-3 jedi-common:刪開發小工具 + 刪死開關 jedi-common 套件 #116 #117 sonnet/medium C
N-4 jedi-issue:刪「送空查詢回全站成員名冊」那條入口(⚠️ 資料表那半待裁,見 D-b3-1) jedi-issue 套件 #121 sonnet/medium D
N-5 FE:拆除「快速設定」獨立畫面兩個入口 FE #119 sonnet/medium E

(五張卡動的是五個不同 repo/套件,彼此不衝突,可以平行派工;序列組只是標記同一張卡內部若有共用檔案的提醒,這批目前沒有跨卡撞檔。)


§3

卡 N-1:BE 側:刪掉主專案自己那份快取連線複製品、改接套件

  • 範圍:BE 主專案 ~/Projects/Billows/Audit-Manager/compliance-manager-be,worktree 建議 wt-fix-b3-be,只涵蓋 M13 第 10 條
  • #9/#66/#67(舊版證據分類三件)不在本卡:由既有卡 CM-1849(FR-107.6 舊線退場+收口)處理。本批不重複開卡、不重複做工。
  • 每件的修法(白話):
    • M13 第 10 條 快取連線複製品——主專案自己那份「連快取伺服器時加密連線不確認對方身分」的複製程式碼
      • 修法:套件那份(現在在 jedi-iam)已修好(開加密就強制確認對方身分、還會驗機器名稱);刪掉主專案自己這份複製品,兩個呼叫端改接套件那份。
      • 🔴 入口清單(已查證,file:line):
        • 要刪的複製品:common/util/redis_client_util.py(全檔 80 行,class RedisClient)——問題那行是 :32 的 ssl_cert_reqs=False(寫死不驗憑證,等同 ssl.CERT_NONE)
        • 套件那份(改接目標):jedi-iam/jedi_iam/common/utils/redis_client_util.py 的 RedisClient——:42-47 是修好的寫法(ssl=True 時強制 ssl_cert_reqs="required" + ssl_check_hostname=True,另支援選填 REDIS_SSL_CA_CERTS)。套件已有回歸測試 jedi-iam/tests/unittest/test_redis_client_util_tls.py(:43 斷言 ssl_cert_reqs == "required"),修法與測試都現成可抄。
        • 呼叫端①(問卷):infra/survey/adapters.py:29-31(函式內 lazy import from common.util.redis_client_util import RedisClient,:31 return RedisClient())
        • 呼叫端②(AI 助手):core/plugins/ai_bot.py:37-39(同樣 lazy import,:39 self._redis = RedisClient())
        • 只有這兩個呼叫端;grep 指令 grep -rn "redis_client_util\|RedisClient" --include="*.py" .(BE),其餘命中都是文件/測試註解(test/test_module_boundaries.py:400、config/config_util.py:4、docs/api/ai-chatbot/generate_docx.py:56,493),不是真呼叫。
      • 🔴 改接時會踩到的一個實質差異(runner 必讀):兩份的設定來源不同,不是直接換 import 就好。
        • 主專案那份(common/util/redis_client_util.py:12-19)是用 ConfigUtils().config 直接讀 REDIS_HOST / REDIS_SECRET / REDIS_SSL / REDIS_USER。
        • 套件那份(jedi-iam/.../redis_client_util.py:18-26)走 jedi_iam.common.settings.get_setting()(ISettingsReader port,由宿主注入),而且 REDIS_SECRET 是當成 JSON 解析(json.loads,取 redis_user_name / redis_user_password 兩個 key),與主專案那份把 REDIS_SECRET 當成純字串密碼、另有獨立 REDIS_USER 的形狀不一樣。
        • 所以要先確認宿主端 ISettingsReader 已接線且 REDIS_SECRET 的格式對得上(主專案的 reader 在 common/iam_ports.py:32 的 EnvSettingsReader,測試 test/test_iam_wiring.py:78-84 有驗它是 ISettingsReader)。格式不對的症狀是「連不上但只寫 log 不拋錯」(套件 connect() 的 except 只 logger.error),會變成靜默失效——問卷驗證碼與 AI 助手對話記錄讀不到但畫面不報錯。
        • 另外套件那份多了 ttl() / incr() 兩支方法、少了任何主專案獨有方法(逐一比對過:主專案那份的 set/get/lpop_key/rpop_key/lpush/rpush/lrem/get_all_from_list/get_last_one_from_list/delete_key/ping 套件全都有),所以呼叫面沒有缺口。
      • 功能不能壞:改接後問卷與 AI 助手兩個功能的快取讀寫(登入狀態、驗證碼、對話歷史)要能正常運作。現在加密未開啟(REDIS_SSL 非 true),行為不應該有變化;只是拿掉重複程式碼,確保之後開加密時只有一套邏輯生效。
  • 這張卡的手測總清單:
    • 問卷功能正常操作一輪(含會用到驗證碼/暫存的流程),確認改接套件版本後行為無異常
    • AI 助手功能正常操作一輪(對話歷史讀寫),確認無異常
    • 看 log/app.log 確認沒有 redis connect 失敗的 logger.error(靜默失效只會在 log 裡看到)
  • 需決策者先裁的:無(#9/#66/#67 已確定歸 CM-1849,原 D-b3-1 取消)

§4

卡 N-2:jedi-task-platform:拆掉三組零呼叫的任務管理功能

  • 範圍:套件 monorepo ~/Projects/Jedicogy/module/jedi-python-package/jedi-task-platform,worktree 建議 wt-fix-b3-task-platform,涵蓋 #68 #69 #120
  • 每件的修法(白話):
    • #68 群組層成員管理——新增/修改/刪除三支程式因系統啟動漏接一條線,整支完全不檢查權限
      • 修法:裁定刪除新增、修改、刪除三個功能整段拿掉(全系統已確認零使用);查詢那支留著給 AI 儀表板呼叫;資料表保留
      • 🔴 入口清單(已查證,file:line;檔案 jedi-task-platform/jedi_task_platform/participant/app/service/project_group_participant_service.py,全檔 137 行):
        • 要刪::62-87 add_project_group_participant()/:90-114 update_project_group_participant()/:117-131 delete_project_group_participant()
        • 要留::25-29 get_project_group_participants()(AI 儀表板呼叫的就是這支)/:36-59 四支 menu / with_inherits 查詢/:134-136 delete_by_project_id()(六層配套刪除,見 #120)
        • 「漏接的那條線」實體位置:BE di_containers/flow_engine/project_participant_containers.py:113-117——project_group_participant_service 的 providers.Factory 只注入 user_service 與 project_group_participant_domain_service,沒有 participant_role_service;對照同檔 :77-85(project)/:87-95(process)/:97-103(control_group)/:105-111(project_control)四支都有帶 participant_role_service=participant_role_service。這支就是唯一漏的那支。🔴 拆除時不要順手把這行補上——補上就變成「修 bug 卻改了行為」,裁定是刪不是補。
        • AI 儀表板呼叫點(要確認不受影響):BE di_containers/dashboard_apis/participant.py:96-107(api_key="participant.get_project_group_participants",method="get_project_group_participants",provider 指向 project_participant_container 的 project_group_participant_service)
      • 零引用查證(七面向,已逐項跑過):
        • ① 套件內:cd ~/Projects/Jedicogy/module/jedi-python-package && grep -rn "add_project_group_participant\|update_project_group_participant\|delete_project_group_participant" --include="*.py" . | grep -v /.venv/ | grep -v /.claude/worktrees/ → 各 1 命中,就是定義本身(零呼叫)
        • ② 主專案:同樣三個方法名在 BE grep -rn ... --include="*.py" . → 0 命中
        • ③ FE:grep -rn "project_group_participant" ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/ → 0 命中
        • ④ DI 容器:BE grep -rn "project_group_participant" di_containers/ → 只有 project_participant_containers.py:113-117(建 service)與 dashboard_apis/participant.py:96-107(申報查詢那支),沒有任何地方申報或呼叫寫入三支
        • ⑤ RLS policy/view/trigger:grep -n "project_group_participants" scripts/init/02-schema.sql → 表結構與 policy 在(資料表保留,不動)
        • ⑥ 出貨基線:同上,資料表保留故不需異動
        • ⑦ migration:grep -rn "project_group_participant" scripts/sql/ → 只有建表/改表,無呼叫面
        • 另有 M10 第 7 條裁定欄原文「全系統已確認零使用」佐證
      • 功能不能壞:AI 儀表板的「群組成員查詢」要照常回資料;DI 那支 Factory 的參數列不要動(只刪 service 內三支方法,Factory 保持現狀)
    • #69 流程參與者管理——一整組從沒啟用過的功能,把關檢查寫在永遠跑不到的位置
      • 修法:裁定刪除新增、修改、刪除三個功能整段拿掉;資料表與回傳格式的定義要留著(別的地方在用)
      • 🔴 入口清單(已查證,file:line;檔案 jedi-task-platform/jedi_task_platform/participant/app/service/process_participant_service.py,全檔 170 行):
        • 要刪::60-80 add_process_participant()/:83-101 update_process_participant()/:104-124 delete_process_participant()
        • 連帶要刪::49-57 _assert_manager_for_process()——這就是「把關檢查寫在永遠跑不到的位置」那支(:55-56 查不到 record 就 return 提前結束,真正的 assert_project_manager 在 :57 永遠跑不到)。它只被上面 update/delete 兩支呼叫(:90、:111),三支刪完它變孤兒,一起刪。
        • 要留::45-47 get_process_participants()(AI 儀表板在用,見下)/:127-136 get_process_participant_menus()/:139-165 get_process_participants_with_inherits()/:168-170 delete_by_project_id()
        • 「回傳格式的定義」實體:participant/app/dto/process_participant_dto.py 的 ProcessParticipantDTO——BE api/flow_engine/serializers/flow_engine/workflow_execution.py:6 直接 import jedi_task_platform.participant.api.serializers.process_participant.ProcessParticipantResponse,這兩個都不能刪
        • AI 儀表板呼叫點:BE di_containers/dashboard_apis/participant.py:31-40(method="get_process_participants")
        • 另一個要留的呼叫端:BE di_containers/flow_engine/workflow_excution_containers.py:142 把 process_participant_service 注入 workflow execution service(注入的是整個 service 實例,刪三支方法不影響注入本身——但要確認 workflow execution service 沒呼叫被刪的三支,見下 ②)
      • 零引用查證(七面向):
        • ① 套件內:grep -rn "add_process_participant\|update_process_participant\|delete_process_participant" --include="*.py" .(排除 .venv/worktrees)→ 各 1 命中,定義本身(零呼叫)
        • ② 主專案:同三個方法名 BE 全 repo grep -rn ... --include="*.py" . → 0 命中(含 workflow_execution_service.py,該注入只用查詢面)
        • ③ FE:grep -rn "process_participant" ~/…/compliance-manager-fe/src/ → 0 命中
        • ④ DI 容器:只有 project_participant_containers.py:87-95 建 service、workflow_excution_containers.py:142 注入、dashboard_apis/participant.py:31-40 申報查詢那支
        • ⑤ RLS/view/trigger:grep -n "process_participants" scripts/init/02-schema.sql → 表與 policy 在(保留)
        • ⑥ 出貨基線:資料表保留,不需異動
        • ⑦ migration:grep -rn "process_participant" scripts/sql/ → 只有建表/改表
        • 另有 M10 第 8 條裁定原文「一整組從沒啟用過」+「資料表在開發環境是 0 筆」佐證
      • 功能不能壞:ProcessParticipantDTO 與 ProcessParticipantResponse 兩個定義不可動(BE serializer 直接 import);流程執行相關畫面(workflow execution)要照常
    • #120 四支任務指派相關功能——沒有對外入口、也沒有任何把關
      • 修法:裁定刪除其中三支(其中一支是被新版取代後留下的舊版,不是「還沒接」);第四支「依專案刪掉所有指派」留著——其他五層都有同名功能,它是配套用的
      • 🔴 入口清單(已查證,file:line;檔案 jedi-task-platform/jedi_task_platform/participant/app/service/task_assignee_service.py,全檔 262 行):
        • 要刪的三支::74-91 get_task_assignee_menu()/:94-132 get_task_assignees_with_inherits()/:135-137 get_user_task_queue()
        • 要留的第四支::260-262 delete_by_project_id()——確認過這就是「依專案刪掉所有指派」,且六層(不是五層,實數六支)都有同名方法:project_participant_service.py:174/process_participant_service.py:168/project_group_participant_service.py:134/project_control_participant_service.py:219/control_group_participant_service.py:207/task_assignee_service.py:260
        • 哪一支是「被新版取代後留下的舊版」:get_user_task_queue()(:135-137)。新版在主專案 app/readmodel/service/my_jobs_app_service.py:47-48 → infra/readmodel/tasks/my_grc_jobs_query.py:70 的 get_user_task_queue_by_sp(),該檔 :71 註解明寫舊 SP public.get_user_task_queue() 是 v1 版、其 join 的表已在 FR-038 Wave 2A 移除。套件那支是舊版殘留。
        • 連帶孤兒:get_user_task_queue() 刪掉後,participant/domain/service/task_assignee_domain_service.py:49-50 與 participant/domain/repository/task_assignee.py:29、participant/infra/repository/task_assignee_repo_impl.py:25 的同名下層方法也成孤兒(那條鏈只被這支呼叫)——要一起刪,不然留下的是從 domain 到 infra 的死鏈
        • 不要誤刪:同檔 :69-72 get_task_assignees()(route 在用,見 participant/api/routes/task_assignee_route.py:50)/:140-173 add_task_assignee()(route :66)/:175-191 batch_add_task_assignees()(route :27)/:193-232 update_task_assignee()(route :81)/:234-258 delete_task_assignee()(route :102)——這五支都有對外 route,全部保留
      • 零引用查證(七面向):
        • ① 套件內:grep -rn "get_task_assignee_menu\|get_task_assignees_with_inherits\|get_user_task_queue" --include="*.py" .(排 .venv/worktrees)→ get_task_assignee_menu 1 命中(定義)、get_task_assignees_with_inherits 1 命中(定義)、get_user_task_queue 5 命中但全在自己那條 service→domain→repo 死鏈內(task_assignee_service.py:135,136/domain/service/task_assignee_domain_service.py:49,50/domain/repository/task_assignee.py:29/infra/repository/task_assignee_repo_impl.py:25),鏈外零呼叫
        • ② 主專案:BE grep -rn "get_task_assignee_menu\|get_task_assignees_with_inherits" --include="*.py" . → 只有 1 個文件命中 docs/api/participant/generate_docx.py:876(文件產生器裡的字串,不是呼叫);get_user_task_queue 在 BE 的命中全屬新版 get_user_task_queue_by_sp 那條(app/readmodel/service/my_jobs_app_service.py:47-48、infra/readmodel/tasks/my_grc_jobs_query.py:70,82、app/flow_control/dto/job_dto.py:281 註解),不是套件那支
        • ③ FE:grep -rn "task_assignee_menu\|assignees_with_inherits\|user_task_queue" ~/…/compliance-manager-fe/src/ → 0 命中
        • ④ DI 容器:di_containers/dashboard_apis/participant.py:12 申報的是 task_assignee_container 的 service 實例(申報的方法不含這三支)
        • ⑤ RLS/view/trigger:grep -n "task_assignees" scripts/init/02-schema.sql → 表與 policy 在(表保留)
        • ⑥ 出貨基線:表保留,不需異動。⚠️ 但舊 SP public.get_user_task_queue() 若還在基線裡,屬另一件事(FR-038 Wave 2B 已換 view),本卡不動 SP、只動 Python 側
        • ⑦ migration:grep -rn "get_user_task_queue" scripts/sql/ → 有 FR-038 系列的 SP/view 改動紀錄,Python 側無關
      • 功能不能壞:五支有 route 的任務指派功能(查清單/單筆指派/批次指派/修改/刪除)要照常;「我的任務」清單走的是主專案新版 get_user_task_queue_by_sp,不受影響;六層 delete_by_project_id() 的專案刪除連動要照常
  • 這張卡的手測總清單(批次驗:一次跑完再一起修,不要逐項):
    • AI 儀表板叫出「群組成員」與「流程參與者」兩個查詢(對應保留的 get_project_group_participants 與 get_process_participants),確認仍正常回資料
    • 專案任務指派五支有 route 的功能各操作一次(查清單/單筆指派/批次指派/修改/刪除)
    • 「我的任務」清單頁正常(驗 get_user_task_queue_by_sp 新版路徑沒被連帶弄壞)
    • 流程執行(workflow execution)相關畫面正常(驗 ProcessParticipantResponse serializer 沒被誤刪)
    • 刪一個測試專案,確認六層 delete_by_project_id() 連動刪除照常
    • BE 起得來並跑一次 pytest test/test_task_assignee_batch.py test/test_fr112_parallel_join_wait.py(這兩支會 import participant service,import 斷裂第一時間會炸)
  • 需決策者先裁的:無(三件裁定都已明確)

§5

卡 N-3:jedi-common:刪開發小工具 + 刪死開關

  • 範圍:套件 monorepo ~/Projects/Jedicogy/module/jedi-python-package/jedi-common,worktree 建議 wt-fix-b3-common,涵蓋 #116 #117
  • 每件的修法(白話):
    • #116 一支開發用小工具混在正式出貨套件裡
      • 修法:直接刪掉(已確認五個程式庫全部零引用)
      • 🔴 入口清單(已查證,file:line):
        • 要刪的檔:jedi-common/jedi_common/utils/gen_comment.py(全檔 106 行)——就是它,檔頭 :1-9 docstring 自己寫「開發輔助腳本:呼叫 OpenAI 幫既有程式碼產 docstring」+「⚠️ 不是產品程式碼,套件執行期沒有任何地方 import 它」
        • 連帶要清的選配依賴:jedi-common/pyproject.toml:29 的註解(「openai 是選配:只有 utils/gen_comment.py 開發輔助腳本用」)+ :31 的 ai = ["openai (>=2.8.0,<3.0.0)"] extra——這個 extra 只為這支而存在,一起刪(M04 報告「更有價值的一點」那段講的就是這個:刪工具連帶省下為它長出來的一圈包裝)
        • 連帶要更新的文件:jedi-common/README.md:66(那一列在講 openai 改選配的理由,工具刪了這列失效)
        • ⚠️ 報告提到「打包時有人特地寫了程式把它排除掉」——jedi-common/pyproject.toml 內未見排除段(只有 extra 宣告),排除邏輯可能在 BE scripts/build/ 或 Nuitka 設定,runner 開工時 grep -rn "gen_comment\|openai" ~/…/compliance-manager-be/scripts/build/ 確認一次;查無就只清套件側三處。
      • 零引用查證(七面向,已逐項跑過):
        • ① 套件 monorepo(21 支):cd ~/Projects/Jedicogy/module/jedi-python-package && grep -rn "gen_comment" --include="*.py" --include="*.toml" --include="*.md" --include="*.cfg" . | grep -v /.venv/ | grep -v /.claude/worktrees/ → 3 命中,全在 jedi-common 自己(README.md:66 文件、pyproject.toml:29 註解、gen_comment.py:30 自己的錯誤訊息字串)。零 import。
        • ② 主專案:BE grep -rn "gen_comment" . --include="*.py" --include="*.toml" --include="*.sh"(排 conversation-history)→ 0 命中
        • ③ FE:grep -rn "gen_comment" ~/…/compliance-manager-fe/src → 0 命中
        • ④ 測試 repo:grep -rn "gen_comment" ~/…/compliance-manager-test → 0 命中
        • ⑤ 授權中心:grep -rn "gen_comment" ~/…/license_center → 0 命中
        • ⑥ 可執行指令(console_scripts):grep -n "scripts\]" jedi-common/pyproject.toml → 未宣告任何 entry point,沒被設成可執行指令
        • ⑦ RLS/migration/出貨基線:不適用(純 Python 檔、不碰 DB)
      • 功能不能壞:純刪檔+刪選配依賴宣告,無執行期路徑。刪完 poetry install / path dependency 要能正常解析(ai extra 消失後,任何寫 jedi-common[ai] 的地方會失敗——已查證無人這樣寫)
    • #117 一個每次連線都會被設定、但全系統沒有任何地方讀它的開關
      • 修法:裁定刪除(留著會讓後人誤以為已有部門層級檢查、反而不去補真正需要的)
      • 🔴 入口清單(已查證,file:line):
        • 那個開關是 app.can_manage_orgs,設定它的程式在 jedi-common/jedi_common/session/database/db.py:185-187:
          session.execute(
              text(f"SET LOCAL app.can_manage_orgs = 't'")
          )
          位於 session_scope() 內「有身分」那一支,無條件執行(每個登入者每次連線都被設成 't')。這三行就是要刪的全部。
        • 旁邊那個「活的」正面對照(報告提到的):同檔 :81-86 的 set_can_read_all_orgs() 設 app.can_read_all_orgs——它是被明確呼叫時才設(BE app/flow_engine/service/workflow_execution_service.py:584/709/905/1176 四處呼叫),而且 scripts/init/02-schema.sql 有 5 條 policy 在讀它(:24730/:25072/:25174/:25700/:25944)。正確形狀就在旁邊,照它的樣子做。
        • 要一起改的文件(否則文件會描述一個不存在的東西)——已查證共 5 份 md(報告說 4 份,實數 5): docs/claude/database-schema.md:1024(「app.can_manage_orgs — org management flag」)/docs/system-design/database/RLS_DESIGN.md/docs/system-design/scripts/generate_db_schema_docx.py:2069 附近(DOCX 產生器的變數表,這支是程式不是 md,改它才會反映到產出的文件)/docs/交付文件/v1.8.0/src/doc-03-database/07-rls.md/另有 FR-080 盤點檔 docs/features/FR-080-2609-jedi-consolidation-and-service-path/inventory/host.md:244(歷史盤點紀錄,屬證據類、不要改)。 ⚠️ FR-085/security-scan-consolidated 那幾份是掃描報告(歷史實況),一律不動。
      • 零引用查證(七面向,已逐項跑過):
        • ① 套件 monorepo:grep -rn "can_manage_orgs" --include="*.py" . | grep -v /.venv/ | grep -v /.claude/worktrees/ → 1 命中,就是 db.py:186 設定它那行(零讀取)
        • ② 主專案程式碼:BE grep -rn "can_manage_orgs" . --include="*.py" → 0 命中
        • ③ FE:grep -rn "can_manage_orgs" ~/…/compliance-manager-fe/src/ → 0 命中
        • ④ DI 容器:不適用(非 service)
        • ⑤ RLS policy/view/trigger:grep -c "can_manage_orgs" scripts/init/02-schema.sql → 0(對照 can_read_all_orgs 有 5 條)
        • ⑥ 出貨基線:同⑤,scripts/init/ 全域 grep -rn "can_manage_orgs" scripts/ → 0 命中(新裝客戶不會有)
        • ⑦ migration:同⑥,scripts/sql/ 零命中
        • ⚠️ 報告已寫明的限制:只查過 DEV 的 policy。其他環境若有人手動加過規則要各自再數一次(唯讀查詢,屬驗收項)。出貨基線是 0,新裝客戶不會有。
      • 功能不能壞:刪這三行不改變任何行為(沒有東西讀它)。唯一的風險是誤刪隔壁 :81-86 的 set_can_read_all_orgs/或誤刪 :185-187 上下方的 app.user_id(:181-184)與 app.is_super_admin(:196-199)——那兩個是活的、有 policy 在讀,刪到就是全庫權限炸掉。逐行比對、不要批次取代。
  • 這張卡的手測總清單(批次驗):
    • 套件安裝流程(poetry install / path dependency 開發模式)正常,確認拿掉小工具與 ai extra 後 import 與相依解析沒斷裂
    • BE 起得來(python main.py)並登入一次——db.py 動到的是 session_scope(),任何 DB 存取都經過它,起不來或登入後查詢全空就是刪錯行
    • 隨便開兩個列表頁(要看得到資料的),確認 RLS 沒被弄壞(誤刪 app.user_id 或 app.is_super_admin 的症狀就是「資料全空」或「看到別家客戶的」)
    • 跑一次 pytest test/test_module_boundaries.py test/test_iam_wiring.py(碰到 session/套件邊界那兩支)確認沒壞
  • 需決策者先裁的:無(兩件裁定都已明確)

§6

卡 N-4:jedi-issue:刪「送空查詢回全站成員名冊」那條入口(⚠️ 資料表那半待裁,見 D-b3-1)

  • 範圍:套件 monorepo ~/Projects/Jedicogy/module/jedi-python-package/jedi-issue,worktree 建議 wt-fix-b3-issue,涵蓋 #121
  • 每件的修法(白話):
    • #121 任何登入帳號送空白查詢就把整張全站成員名冊(含電子郵件)撈回來
      • 修法:已裁定整塊移除:入口、資料表、同步程式一起拿掉,不補隔離、不留著
      • 🔴 入口清單(已查證,file:line;套件 jedi-issue):
        • 網址入口:GET /api/1.0/issue/get_members → route jedi_issue/api/routes/issue_member_route.py:11-15(IssueMemberRoute.get(),:12 只有 @auth_required=只驗登入)
        • 掛載點:jedi_issue/api/routing.py:19(import)+ :24(api.add_resource(IssueMemberRoute, "/issue/get_members"))+ :36("/api/1.0/issue/get_members" 那份網址清單)
        • 主專案側掛載:BE core/plugins/issue.py:186-188(register(app, …, mount_api=True))——這是整包掛載,不是逐條,所以主專案不需改;另 core/plugins/issue.py:176 與 config/app_modules.py:90 兩處註解提到這條網址,刪完要順手更新
        • service:jedi_issue/app/member/service/member_service.py:13-21 get_members()(就是「送空白查詢回整張表」那支)
        • DI 接線:jedi_issue/plugin/assembly.py:58-59(_member_service() 工廠)+ :82(member_service=given.member_service or _member_service)+ jedi_issue/plugin/contract.py:75(member_service 欄位宣告)+ jedi_issue/plugin/runtime.py:35/:48 兩處註解
        • repo/mapper/model/entity/domain service:jedi_issue/infra/member/repository/member_repostiory.py(:23 就是 .query(Members).all() 回全表)/jedi_issue/infra/member/mapper/member_mapper.py/jedi_issue/infra/member/models/member.py:6-19(Members model,:16 是那個 email 欄)/jedi_issue/domain/member/{repository,service,entities}//jedi_issue/app/member/dto/member_dto.py
        • 資料表:public.members(出貨基線 scripts/init/02-schema.sql:14136 的 CREATE TABLE public.members)
      • 🔴🔴 重大:材料說「零引用」,實際有引用——這件不能照「整塊移除」直接做(本棒查證推翻前一棒結論,開卡務必寫進去):
        • Members model 與 members 表被「意見回饋」那條活路徑用著,不是死程式:
          • jedi_issue/infra/issue/repository/member_repository.py:7-17 get_members_by_username() → 被 jedi_issue/infra/issue/adapter/local/local_issue_adapter.py:161 呼叫(建 issue 時把 assignees 名字換成 member 列)
          • jedi_issue/infra/issue/repository/issue_assignee_repository.py:9/22/28/39-40/46 全部 join(Members, …) 或吃 list[Members]
          • jedi_issue/infra/issue/adapter/local/local_issue_adapter.py:90/103/131-132 讀 assignee 的 username 靠這張表
          • jedi_issue/infra/issue/adapter/issue_details.py:13 的 assignees: list[Members] 型別宣告
          • 而 Local provider 是活的:jedi_issue/infra/issue/adapter/__init__.py:18 register_issue_adapter("Local", LocalIssueAdapter),jedi_issue/app/feedback/service/feedback_issue_service.py 有十幾處 provider=IssueProviderCode.LOCAL(:83/:88/:102/:157/:208…)——意見回饋功能走的就是 Local
          • 另有 public.issue_assignee_mapping 表(基線 scripts/init/02-schema.sql:13674)以 member_uid 外參 members.uid
        • DEV 實查(2026-09-21):public.members 0 筆、public.issue_assignee_mapping 0 筆——所以「現在沒資料」是真的,但那是因為意見回饋目前沒人指派負責人,不是因為程式沒接。members 表現在是空的、將來一有人用「指派負責人」就會有資料。
        • 因此本卡要拆成兩半,而且第二半要先裁:
          • 可以直接做的:刪掉 /issue/get_members 這條外洩入口(route issue_member_route.py、routing 兩處、MemberService.get_members()、DI 三處接線、MemberRepoImpl.get_members() 那支回全表的查詢)——這半是真的零呼叫(見下零引用查證),刪掉不影響意見回饋。
          • 不可以直接做的:刪 members 資料表、刪 Members model、刪「同步程式」(create_members/update_member/delete_member)——刪了意見回饋的「指派負責人」整條會炸(local_issue_adapter.py:161 會 AttributeError/SQL 找不到表)。M21 第 2 條的「整塊移除(入口、資料表、同步程式一起拿掉)」這個裁定是建立在「全是死程式」這個前提上的,而該前提本棒查證不成立。
      • 零引用查證(七面向,針對「入口那半」):
        • ① 套件內:cd ~/…/jedi-python-package && grep -rn "get_members\b" --include="*.py" . | grep -v /.venv/ | grep -v /.claude/worktrees/ | grep -v /tests/ → 命中只有 issue_member_route.py:14(呼叫)+member_service.py:13(定義)+member_domain_service 對應一支;get_members_by_username 是不同方法、不要混
        • ② 主專案:BE grep -rn "get_members\|IssueMemberRoute" --include="*.py" .(排 conversation-history)→ 只有註解與文件產生器(core/plugins/issue.py:176、config/app_modules.py:90、docs/api/issue/generate_docx.py:40/281/286/299/335/348/351),無程式呼叫
        • ③ FE:grep -rn "get_members" ~/…/compliance-manager-fe/src/ → 0 命中(前端沒有任何地方打這條網址;docs/api/issue/generate_docx.py:348 那句「此端點主要供前端 Issue 指派功能使用」是過期文件,實查不成立)
        • ④ DI 容器:BE 側無獨立申報(整包 register() 掛載);套件側接線三處已於上方列出,屬「要一起刪」不是「有人在用」
        • ⑤ RLS policy/view/trigger:grep -n "public.members\|ON public.members" scripts/init/02-schema.sql → 該表零 policy、零 tenant 欄位(正是這條的病灶)
        • ⑥ 出貨基線:scripts/init/02-schema.sql:14136 有建表,:13674 有 issue_assignee_mapping——兩張都在基線裡,動它們就要重產基線
        • ⑦ migration:grep -rn "members" scripts/sql/ 與 jedi_issue/plugin/migrations.py(後者零命中)→ 無獨立 migration,表是隨基線長出來的
  • 這張卡的手測總清單(批次驗):
    • GET /api/1.0/issue/get_members 回 404(路由完全消失,不是「還在但改回錯誤」)
    • 意見回饋整條回歸測一輪(這是本卡真正的風險面):送一則回饋、指派負責人、看列表、看單筆、改一次、關閉一次——確認 members 相關程式碼還在、功能不炸
    • BE 起得來並跑一次套件既有測試 pytest tests/unittest/test_member_service.py tests/integration/test_service_behaviour.py(在 jedi-issue repo 內)——⚠️ 這兩支測的就是被刪的方法,刪完必然要同步刪對應測試案例,不是「跑過就好」
  • 需決策者先裁的:
    • D-b3-1(新增,取代原 D-b3-1):M21 第 2 條裁「整塊移除(入口+資料表+同步程式)」,但本棒查證發現 members 表與 Members model 被意見回饋的「指派負責人」路徑實際使用中(local_issue_adapter.py:161、issue_assignee_repository.py 多處 join、Local provider 是活的;DEV 兩張表 0 筆只是因為目前沒人指派)。所以:這張卡只拆「送空查詢回全表」那個外洩入口(可直接做),資料表與同步程式先留著,還是要連帶把意見回饋的指派負責人功能一起改掉/停用(範圍大得多、要另開卡)? 我的建議:本卡只拆入口(入口拆掉洞就補上了——外洩面是那條網址,不是那張表),資料表與 model 保留給意見回饋用;至於「members 表沒有客戶歸屬欄位、零隔離」這件另案處理(掃描總表 §3.1 第 7 項已登記 jedi-issue 五張表零隔離,本來就要走加欄位+補 policy 的 migration,與本卡的「刪死程式」性質不同)。這樣本卡不必動出貨基線、不必重產基線。

§7

卡 N-5:FE:拆除「快速設定」獨立畫面兩個入口

  • 範圍:FE ~/Projects/Billows/Audit-Manager/compliance-manager-fe,worktree 建議 wt-fix-b3-fe-quicksetup,涵蓋 #119
  • 每件的修法(白話):
    • #119 獨立的「快速設定」畫面——按下去顯示成功但一筆都沒存,兩個入口網址都沒有任何按鈕導過去
      • 修法:決策者裁定整頁拆除,兩個入口都要拆(一般版與稽核計畫版兩個入口網址)。系統裡另有一個「專案規劃頁」內的快速設定已改走新做法、會正常存,那個不受影響、不要動;只拆這個獨立的舊畫面
      • 🔴 入口清單(已查證,file:line;FE repo,注意路由檔在 src/config/router/index.js,不是 src/router/):
        • 入口①(一般版):src/config/router/index.js:911-915
          • path /project/projects/:id/task-setup/name project-task-setup/component @/views/project/TaskSetupView.vue/meta breadcrumb parent project-auditor-overview
        • 入口②(稽核計畫版):src/config/router/index.js:962-966
          • path /project/projects/:id/ap/:apUid/task-setup/name project-task-setup-ap/同一個 component @/views/project/TaskSetupView.vue/meta breadcrumb parent project-auditor-overview-ap
          • 🔴 兩個入口共用同一支元件,所以「刪元件」只能在兩條路由都拆掉之後做——只拆一條就會讓另一條 500(比照 memory feedback_parallel_runners_two_entrypoints_one_fixed,兩個入口要列成兩個獨立驗收項、對稱檢查)
        • 要刪的畫面元件:src/views/project/TaskSetupView.vue(1,421 行,已超 800 行上限——刪掉正好減負,不需拆檔)
        • 連帶要清的 i18n(四份,兩語系×兩類):
          • src/config/locales/i18n/zh-tw/menu.json:108(project-task-setup)、:113(project-task-setup-ap)、:239、:243(兩條 description)
          • src/config/locales/i18n/en/menu.json:108、:113、:239、:243(同上四處)
          • src/config/locales/i18n/zh-tw/task-setup.json(整檔 162 行,專屬此頁)+ src/config/locales/i18n/en/task-setup.json
          • src/config/locales/index.js:34(import enTaskSetup from '@/config/locales/i18n/en/task-setup.json')+ :114(twTaskSetup)——刪 json 要一併刪這兩行 import 與下方註冊處,不然 build 直接炸
        • 要留的兩處字串(只是提到、不是入口):
          • src/views/project/ProjectPlanningView.vue:281 註解「任務資料(同 TaskSetupView)」——這頁是要保留的新做法(:371/:1744/:1789/:2585 是它自己的快速配置實作),刪 TaskSetupView 後把這句註解改掉即可
          • src/views/workflow/WorkflowSetupEditor.vue:13 註解列了「五支活頁面用程式開它(TaskSetupView / ProjectPlanningView / …)」——刪完要把 TaskSetupView 從這句移除,否則下一個人照註解找不到檔
        • 另一處疑似(runner 開工先確認):src/config/locales/i18n/zh-tw/task-setup.json:146 有 "btn_quick_config": "快速配置",而 src/views/project/TaskSetupView.vue:340 有註解「目前登入者有權限配置的 AO 數量(用於快速配置 hint)」——確認這個 key 只被 TaskSetupView 用(grep -rn "btn_quick_config" src/),若 ProjectPlanningView 也在用就不能連 json 整檔刪、要留那一個 key
      • 零引用查證(FE 面向,已跑過):
        • 路由名零引用:grep -rn "project-task-setup" src/ | grep -v "src/config/router/index.js" → 8 命中全在 i18n menu.json(zh-tw 與 en 各 4 處),沒有任何 router.push / <router-link> / 選單設定指向它——證實材料說的「兩個入口都沒有任何按鈕或連結導過去」
        • 元件零引用:grep -rn "TaskSetupView" src/ | grep -v "src/config/router/index.js" → 2 命中皆為註解(ProjectPlanningView.vue:281、WorkflowSetupEditor.vue:13)
        • path 字面零引用:grep -rn "task-setup" src/ | grep -v "src/config/router/index.js" → 只有上述 i18n 與 locales/index.js 的 import 兩行
        • BE 側無關:這頁的「按了不存」是因為 BE 那條已固定回空,本卡不動 BE(該 BE 端點的處置屬另案)
      • 功能不能壞:「專案規劃頁」(ProjectPlanningView.vue)內走新做法的快速配置完全不受影響、不要動(:371/:1744/:1789/:2585 四段是它自己的實作);刪 i18n json 一定要同步刪 locales/index.js 的 import 與註冊,否則 npm run build 直接失敗
  • 這張卡的手測總清單(批次驗,兩個入口對稱列):
    • 入口①:手動輸入 /project/projects/<id>/task-setup,確認畫面已完全移除(路由不存在,不是空白頁或錯誤畫面)
    • 入口②:手動輸入 /project/projects/<id>/ap/<apUid>/task-setup,確認同上
    • npm run build 通過(i18n import 沒刪乾淨會在這裡炸)
    • 切英文語系再跑一次上面兩個入口(驗 en menu.json 也清了)
    • 「專案規劃頁」內的快速配置正常操作一輪、能正常存檔(回歸,不能被誤刪)
    • 流程設定編輯器(WorkflowSetupEditor)能從其餘四支活頁面正常開啟(驗註解改動沒動到程式)
  • 需決策者先裁的:無(裁定已明確,兩個入口都要拆)

§8

決策者要裁的事(彙整本批的 D)

  • D-b3-1(本棒查證後新增,取代原本那條):jedi-issue 的「全站成員名冊」要不要照原裁定把資料表與同步程式一起刪? M21 第 2 條裁「整塊移除(入口+資料表+同步程式)」,前提是「全是死程式」。本棒開檔查證發現這個前提只有一半成立:
    • 「送空白查詢回整張名冊」那條網址入口確實零呼叫(BE/FE/套件三面向都查了,只有註解與過期文件提到它)——這半可以直接刪。
    • 但 members 資料表與 Members model 被「意見回饋」的「指派負責人」路徑實際用著(local_issue_adapter.py:161 拿 username 換 member 列、issue_assignee_repository.py 多處 join、意見回饋走的 Local provider 是活的)。DEV 那兩張表 0 筆,是因為目前沒人用指派負責人,不是因為程式沒接。 我的建議:本卡只拆入口,資料表與同步程式保留(洞在那條網址、不在那張表;拆掉入口洞就補上了)。這樣也不必動出貨基線、不必重產。至於「members 表沒有客戶歸屬欄位、零隔離」那件,掃描總表 §3.1 第 7 項已另有登記(jedi-issue 五張表零隔離,要走加欄位+補 policy 的 migration),性質是「補隔離」不是「刪死程式」,與本卡分開。
    • 若決策者裁「連表一起刪」,則本卡要擴大到「意見回饋的指派負責人功能一併停用」,範圍跨 BE+套件+FE+出貨基線重產,不適合留在這批,建議另開卡。
  • 另一件要註記的:原計畫的 #9/#66/#67(舊版證據分類三件)已確定由既有卡 CM-1849 處理,本批不開卡、不重複做工;5B-5 驗證卡改成「等 CM-1849 做完再驗」。