本卡屬 FR-095(母卡見本 arc README front matter),第 1 棒 P1:專案成員新增/移除/角色指派+守門入口+掛載契約(jedi 套件 repo)。只掃不修。
「誰是這個專案的成員、他是管理者還是只能看、誰負責哪個控制項」——這一棒掃的是管理這些名冊的 API(新增成員、移除成員、改角色、查名冊)從網址進來、到「檢查你有沒有資格改名冊」的那段路,以及所有守門共用的那個入口(participant/common/guard.py)與套件掛進主專案的契約(plugin/)。共 39 檔(30 支主檔+9 支 DTO,DTO 就是「API 收進來/回出去的資料長什麼樣」的定義檔)。只找問題、不修問題。
這是整個 arc 門檻最低的一組:五支「讀名冊」的端點只驗登入,專案編號由呼叫端自己填,程式直接拿去查、不問「你是不是這個專案的人」——登入+知道任一專案編號就能讀走別家客戶的成員名單(帳號、角色、部門)。而 common/guard.py 是六支成員服務共用的守門入口,只有把入口和六支服務放同一棒,才看得出「誰有接、誰沒接、接了但條件式繞過」。
這些是首腦讀過程式碼後認為最容易出事的地方,是思考起點不是檢查清單。每條後面的「首腦核對」是首腦親自開檔的結果——✅ 屬實的不必重做核對,直接當錨點追下去;標「待 runner 核對」的才要你自己開檔確認。工具沒答的自己開檔查了再寫,標明「(工具未報,人工查證)」:
project_id 呼叫端自填(疑點 #7):participant/app/service/project_participant_service.py:43-45 的 get_project_participants(**kwargs) 直接 get_all(ProjectParticipantQueryEntity(**kwargs)),**kwargs 就是「呼叫端丟什麼條件就查什麼」。對應端點 POST /project-participants、GET /project-participants/menu、POST /project-control-participants、GET /project-control-participants/menu(另一支 POST /task-assignees 歸 P2)。為什麼可疑:讀名冊是資訊外洩的第一步,也是本專案「讀端點漏守門」第五次出現。首腦核對:✅ 屬實。要追:route → service → repo 三層有沒有任何一層補上「呼叫者必須是該專案參與者」。return 之後(疑點 #1):participant/app/service/process_participant_service.py:55-56 原文 if record is None or record.project_id is None: return,下一行才是 assert_project_manager;docstring 自陳「查無 record 則交後續 validate 拋 NotFound」。為什麼可疑:撈不到就整段跳過,等於「拿一個不存在的編號就不會被檢查」;要追後續 validate 有沒有真的拋、以及有沒有路徑撈得到紀錄但 project_id 為空。首腦核對:✅ 屬實。if enforce_role and self._participant_role_service: 才呼叫 assert_project_manager。participant/app/service/project_group_participant_service.py 全檔 grep assert_\|enforce_role\|participant_role_service 零命中——它是漏注入的那支(宿主 DI 那半歸 H1)。目前 participant/api/routing.py 沒掛它的 route,但 AI 儀表板讀得到。為什麼可疑:DI 漏注入=靜默全開,不報錯、不留 log。首腦核對:✅ 屬實。要交:六支 service × 每個寫入方法的守門條件對照表。project_id/group_id/control_id 沒人驗證彼此真的相屬(疑點 #9):participant/api/routes/control_group_participant_route.py:26-31、project_control_participant_route.py:86-93 全由 body 帶入。為什麼可疑:可在自己是 manager 的專案下,寫入一筆指向別的專案的 group/control 的參與者列——守門只查「你是 project_id 那個專案的 manager」,不查「group_id 屬不屬於這個專案」。首腦核對:讀碼助手報,首腦未開檔,待 runner 核對。participant/common/user_directory.py:49-113 七處「adapter 沒這方法就回空 dict+一行 WARNING」;宿主 core/plugins/participant.py:142-146 紅字警告自陳這是刻意設計。為什麼可疑:註解寫「刻意」,工具會被說服跳過;要確認的是「查不到人」有沒有任何一條路被解讀成「不是參與者→跳過檢查」而不是「拒絕」。首腦核對:未開檔,待 runner 核對。participant/common/guard.py:26-52——_guard is None 時 :45-49 raise RuntimeError 拒絕執行(這一段是對的)。要追的是 configure() 有沒有可能被呼叫兩次、或被傳入 None 而把已接好的守門清掉;以及 plugin/assembly.py/runtime.py/contract.py 的掛載契約有沒有讓宿主「少傳一個 adapter 也能啟動」。(工具未報時人工查證)participant/api/guards.py:10-12 CAPABILITIES 空 tuple):入口只剩宿主注入的 jwt_required。這是設計自陳「參與者授權看專案角色不看能力點」——本棒不重審這個政策,只審「每一條 route 有沒有真的被 jwt_required 包住」(routing.py:52-57 的 R() 包裝在 auth_required=None 時直接回原 class,要看宿主傳了沒——宿主那側歸 H1,本棒只記套件側的行為)。授權判定怎麼流(首腦 2026-09-14 開檔核對,行號可直接打開):
common/authz/project.py(120 行):assert_project_manager :16-30/assert_project_role_fallback :33-57/assert_project_role :60-98/assert_project_participant :101-120。守門函式本身沒有 fail-open(fail-open 就是「查不到就放行」的壞習慣):查不到角色就 raise、沒有拿租戶代替專案、沒有 super_admin 短路。participant/app/service/participant_role_service.py:33-83,三層往上找:project_control_participants → control_group_participants → project_participants → 都沒有就回 None。core/plugins/participant.py:90-102 的 adapter → 套件 participant/common/guard.py:26-52 的 configure();沒接線時 :45-49 直接 raise、不放行(這一段是好的)。task 那一半的三道守門(license/project_role/identity)在 core/plugins/task.py:89-98,與 jedi-compliance-audit 共用同一組 adapter。