本卡屬 FR-109(母卡見建卡後補號),第 2 棒:建題那半——問卷、資料夾、分頁、題目、討論五條 CRUD,Excel 匯入,複製/搬移/發佈,api 守門殼與 route 表,plugin 五檔,migration。50 檔,只掃不修。
管理者建問卷、分頁、題目、放進資料夾、從 Excel 匯入、複製一份、搬到別的資料夾、發佈給任務用;填答者可以在題目底下留言討論。這棒驗:四顆能力點是不是每條寫入 route 都掛了、討論的改刪有沒有驗本人、Excel 匯入會不會炸、複製時租戶會不會跑掉、守門殼缺零件會不會拒絕啟動。
建題側是「能力點守門」的形狀(與填答側的「歸屬守門」不同),且含 Excel 解析與 signed-token 範本下載這兩種本專案常出問題的面。守門殼與 plugin 放這棒是因為它們與 route 表在同一層。
survey_route.py 首腦已核:讀取只掛授權、寫入七支全掛能力點)。要逐支對另外四檔:survey_folder_route.py(grep 4 處 capability)、survey_page_route.py(6)、survey_question_route.py(6)、survey_discussion_route.py(3)——每條寫入 route 掛的是哪顆、有沒有掛錯(例如刪除掛 update)、reorder/move 這種「改結構」的動作掛什麼。列表對照。app/service/survey_discussion_service.py:69-80)。update_discussion(uid, message, user_name) 與 delete_discussion(uid) 都不比對 created_user,delete_discussion 連 user 都不收。route 掛的是哪顆能力點——若掛 survey.update,等於有改問卷權限的人可刪任何人的留言。與 CM-1630 同型(🅰 組),標明。survey_route.py:171-216、app/service/survey.py:215-448)。tempfile.NamedTemporaryFile(delete=False)+os.remove——例外路徑會不會留檔;load_workbook 有無 read_only/大小上限;process_excel_options 用 ||/:: 切字串——選項內容進 DB 後被前端怎麼渲染(XSS 面在前端,這裡只標資料未清洗);import_survey(folder_uid, …) 的 folder_uid 能不能指到別租戶的資料夾(survey_folders 有租戶欄、RLS 兜底)。survey_route.py:218-240)。issue_signed_token(scope) 只綁 scope 不綁 resource(自陳靜態範本),signed_token_required(scope=…)——token 有效期、能不能拿去打別的 scope;jedi-iam signed_token.py 那支在 FR-101 複查時抓到「沒帶 ?st= 退回只驗登入」的假修(總表第 34 項),這裡有沒有同款。survey_clone_service.py、survey.py:110-215/:449、domain/service/survey_snapshot_domain_service.py)。clone_survey 新列的 tenant_id 從哪來(before_flush 自動補=呼叫者租戶,正確;若顯式帶來源的則錯);as_new_version=True 時舊版本誰能改;move_survey(target_folder_uid, …) 目標資料夾歸屬;create_snapshot(source_survey_uid, project_uid) 給任務用的快照——跨租戶的 source 能不能被 snapshot(surveys RLS 擋讀,但 snapshot 在 @transaction 內用誰的身分)。api/guards.py、plugin/assembly.py、plugin/contract.py、plugin/task_type.py)。模組級全域接線的已知取捨(同行程兩 app 覆寫)確認即可;_assert_wiring 缺 license_guard/capability_required 拒絕掛載——auth_required 缺了呢(SurveyAdapters 各欄 Optional?);mount_api=False(socketio 模式)時 runtime 還寫不寫;能力點宣告 4 顆 vs 實掛(①的結果)。survey_folder*、*_trans 表)。survey_folders 有租戶欄;*_trans 三張靠 join 反查——policy 鏈完整嗎(V2 ⑦ 同題,本棒看建題側的表:surveys_trans/survey_pages_trans/survey_questions_trans)。app/common/task_survey_guard.py 檔頭自陳「填答與管理端點原本只有 route 層 @jwt_required,service 完全無身分檢查——任何登入者可改任何人的填答」,D10 定調後補了 assert_task_survey_writer(填答=被指派者本人或專案 manager)與 assert_task_survey_manager(管理=專案 manager)。本系列主要目的是驗補上去的洞有沒有真的堵住、有沒有漏掉入口。.3a 問卷 12 張裸表隔離 已 Done);只有 surveys/survey_folders 有 tenant_id 欄,其餘 13 張靠 policy 內 EXISTS(... JOIN ...) 反查到有租戶欄的表——這種「靠 join 反查」的 policy 是本系列要驗的重點之一(join 鏈斷了就變全開或全關)。compliance.job_execution_surveys 零隔離但屬 flow-control。4 顆能力點 survey.* 全客戶層。surveys 表資料量與 question_answers 見卡內數字。api/guards.py 檔頭自陳它是 21 支裡唯一用模組級全域存接線的(process 級非 app 級,同行程兩個 app 第二次 configure() 覆寫第一次),_assert_wiring 缺接線拒絕掛載。確認即可,不當漏洞報。core/plugins/survey.py:178 mount_api=not host.enable_socketio),但 socketio 的 fill-survey namespace handler 在套件內(app/handler/fill_survey_socketio_handler.py)——它的房間加入有沒有驗身分是 V2 重點。docs/features/FR-049-2607-survey-flow-rules/design.md(問卷流程規則)、FR-049.1-2607-survey-display-conditions/design.md(顯示條件)、FR-021/FR-023(填答重複與 checkpoint)。