狀態:探脈完成,待首腦寫討論稿 | 日期:2026-10-12 | 卡片:Notion CM-2715(母卡 CM-2714)
路徑慣例:BE 路徑相對於 compliance-manager-be/;套件路徑以 pkg/ 代表 ~/Projects/Jedicogy/module/jedi-python-package/;FE 路徑相對於 compliance-manager-fe/。DEV 查詢一律以 cmmgr 唯讀執行。
§1
一句話結論
- 任務上沒有任何時間軸欄位(到期日、週期、下次執行),也沒有「複製單筆任務」的方法;最接近的是覆核輪的「整輪重生+逐對承襲」。
- 型別申報表只有「啟動時」一個掛鉤真的會被呼叫;
config_schema 和 status_machine 有申報、平台不讀。完成、到期、取消時都沒有掛鉤。
- 排程骨架現成:APScheduler 已有 12 支 job、寫法統一,有每 15 分鐘的先例;長工作走
background_jobs 佇列,也有「排程只丟單、worker 執行」的先例。但多副本之間沒有鎖。
- 寄信能在背景跑,但沒有寄信紀錄表、沒有使用者語系欄位、也沒有「哪些事件要通知誰」的設定。
- 檢測工具可以排未來時間(
agent_tasks.scheduled_at 拉單時會過濾),SBOM 伺服器端掃描不行;SBOM 重新執行沿用上次上傳的源碼包。兩條掃描路「掃完」會匯合在同一處,那裡可以掛門檻判斷,但現在完全不看嚴重度。
- 資源庫
frequency 欄沒有任何排程邏輯在讀;同表還有兩個從沒被寫過的 last_execute_date/next_execute_date。前端有四處把六個選項寫死。
- 問卷任務建立時會複製快照,但「填完 → 任務完成」中間沒有自動串接;填完的問卷回覆狀態機規定不能重開。
§2
① 任務本體
資料表與欄位
- ORM 在套件裡:
pkg/jedi-flow-engine/jedi_flow_engine/infra/models/job_execution.py:15-41。
- DEV
compliance.job_executions 實際欄位:uid, main_workflow_execution_id, workflow_execution_id, template_id, template_job_id, status, type, start_time, end_time, id, created_at, updated_at, created_user, updated_user, name, description, tenant_id, org_unit_id, job_type, guide。
- 沒有到期日、週期、下次執行時間、
parent_round_id。start_time/end_time 是實際開始/結束時間。
- 「屬於哪一輪」記在
workflow_execution_control_mapping.round_id(app/flow_control/service/prep_job_generation_service.py:257-260),母輪欄位在輪次表(pkg/jedi-compliance-audit/.../infra/model/project_audit_round.py:32)。
- DEV 分布:
job_type general 64,446/detection_tool 39/survey 3;status TODO 64,159/PROCESSING 249/COMPLETED 80;type 全部 USER。
任務的四條建立路徑(不是同一支依型別分流)
- 首輪:
ProjectStartAppService 呼叫 generate(reclone_from_props=False)(app/project/service/project_start_app_service.py:315)。核心在 prep_job_generation_service.py:119:每個評估目標各複製一份流程範本,建 workflow_execution 與 job(235-241,固定 TODO/USER,不設 job_type,落成 general)。
- 覆核輪:
pkg/jedi-compliance-audit/.../audit_round_app_service.py:596 launch_reverify,在 774-781 以 reclone_from_props=True, parent_round_id=… 呼叫同一支 generate() 整輪重生;788 再呼叫 ReverifyInheritanceService.inherit_from_parent(app/flow_control/service/reverify_inheritance_service.py:34),用 get_job_pairs(parent_round_id, new_round_id)(infra/flow_control/repository/reverify_clone_query.py:31)把新舊任務配對,搬指派、部門、設備、證據、問卷(69/84/94/104/143)。
- 問卷與檢測工具任務:PM 手動建,
JobService.create_job(app/flow_control/service/job_service.py:195)→ repo create_job(infra/flow_control/repository/flow_control_job_repo_impl.py:639,job_type 寫入在 738);之後型別分支寫死:問卷建快照(job_service.py:233-237)、檢測工具 if … job_type == DETECTION_TOOL_JOB_TYPE(243)。檔頭 68-71 自註「型別分支待改成 handler 派發」。
- 流程圖新增節點:
create_job_for_node(repo:1015),固定建 general(1034)。
狀態機
JobStatus 四態 TODO/PROCESSING/COMPLETED/CANCEL:pkg/jedi-flow-engine/jedi_flow_engine/common/enum/job_code.py:3-7。
- TODO → PROCESSING:
activate_jobs(infra/flow_control/repository/task_execution_query.py:106),「開始執行任務」觸發。
- PROCESSING → COMPLETED:
complete_job(app/flow_engine/service/workflow_execution_service.py:648;非 PROCESSING 擋在 667,寫入 672-674)。批次完成 job_batch_complete_service.py:42(general 要有附件、survey 要已填完,81-85)。
- 退回:
revert_job(workflow_execution_service.py:811)。
- CANCEL 沒有任何寫入點,只出現在「算已結束」的判斷裡。
「重開一筆任務」最接近的既有能力
- 沒有單筆任務複製的方法。
- 覆核輪:輸入是「控制項清單+round_id」(generate:119),承襲輸入是 new_round(inheritance:34);
clone_* 系列吃「舊任務→新任務」配對,前提是新任務已存在。
create_job_for_node(repo:1015):在既有流程上建一筆空 job,不帶承襲。
flow_template_app_service.duplicate(app/flow_engine/service/flow_template_app_service.py:264):複製範本,不是任務。
§3
② 任務型別申報制
TaskTypeSpec 欄位(pkg/jedi-task-platform/jedi_task_platform/task/domain/task_type_registry.py:31-89)
誰註冊了哪些型別
- 集中接線
core/plugins/task.py:98-101,只申報兩種:
survey_task_type_spec():pkg/jedi-survey/jedi_survey/app/task_type_declaration.py:61-70(狀態機+review_required 設定,無掛鉤)。
detection_tool_task_type_spec():app/detection_tools/task_type_declaration.py:101-109(on_tasks_activated=_auto_dispatch_first_scans)。
- 登記方式
assembly.py:33-42 register_job_type(replace=True)。
- general 不在 BE 申報,是登記表內建(registry:157-158,撤不掉 147-151)。
- DEV
system_menus 的 TASK_TYPE 組有 task/general/survey/file_upload/exec_tool 五個 key,與申報表(general/survey/detection_tool)對不上。
原則(檔頭)
- registry 1-19:型別真相由插件申報,平台只按代號查表,不寫回平台枚舉。
- registry 80-82:平台不得持有業務編排、不得寫死某型別啟動時做什麼。
- 檢測申報檔 47-51:派掃描是「產品業務規則」,所以從平台搬出。
- 問卷申報檔 1-25:審核要不要做是「設定」不是「客製」,預設在程式、實際值存宿主
system_configs。
config_schema 能不能放「週期」
- 讀取端沒有人走登記表;問卷自己讀
review_required(config_reader)(問卷申報檔 73-115),值是租戶層級 SURVEY_CONFIG/review_required(51-53)。
- 任務本身沒有存設定的欄位。唯一按任務存參數的是
config.job_execution_detection_tools.tool_params JSONB(scripts/sql/2026-07-26-fr056-2-job-execution-detection-tools.sql:11),只限檢測任務。
- 結論:放
config_schema 只能當租戶預設、平台不讀;放任務上則沒地方存。
掛鉤
- 唯一呼叫處:
task_execution_service.py:94 → _run_activation_hooks(105-140,每支包 SAVEPOINT,失敗只記 warning)。
complete_job(648)、revert_job(811)都沒查登記表;完成、到期、取消、退回皆無掛鉤。
§4
③ 排程
現有 12 支 job(core/scheduler.py)
共通寫法
@_job_scoped("名稱")(24-39,log 帶 job-<名>-<hex>)→ try: → with app.app_context(): → 函式內 import system_context → 取 service → with system_context("名稱")(有時加 session_scope())→ 呼叫 → except 記 logger.exception。全部 max_instances=1, coalesce=True。
- 取 service 兩種:DI 容器
app.extensions.get('di_container').<container>.<service>()(167-176);套件 job 用 app.extensions[HANDLE_KEY](374-376、334)。
- 執行緒池預設 4(157;讀
DRIVE_SYNC_WORKER_POOL_SIZE,core/app_factory.py:366-367)。
誰起排程、會不會重複跑
- 只有
RUN_MODE=api 起業務 job(main.py:153,161-165;模式表 main.py:13)。socketio/worker 只起完整性抽查(main.py:204-210、core/scheduler.py:111-139)。
- 單一 api 行程只跑一份(只在 gunicorn master,
main.py:155-160、core/scheduler.py:558-559)。
- 多個 api 副本之間沒有分散式鎖(advisory lock 只在
infra/setup/setup_provision_lock.py:21);防重全靠各 job 冪等,例如 cert_expiry_notified_at 條件更新(core/scheduler.py:365-366)。
background_jobs(RUN_MODE=worker)
- 入列
BackgroundJobAppService.enqueue(job_type, payload, max_attempts=3),需要登入者與租戶,會存建單者身分(app/background_job/service/background_job_app_service.py:55-71)。
- handler 註冊
@register_job_handler(job_type),重名直接 raise(app/background_job/handler_registry.py:25-32);現有 4 支(core/plugins/_source_scan_vulndb.py:87、_source_scan_vulndb_fetch.py:146、_evidence_classification_job_queue.py:27、source_scan.py:142)。
- 撿單每
WORKER_POLL_SECONDS(預設 2 秒),有租約與心跳(app/background_job/worker_runner.py:28-61);FOR UPDATE SKIP LOCKED(infra/background_job/repository/background_job_repo_impl.py:32)。
- 無延後執行欄位(DEV 欄位:
id, uid, tenant_id, org_unit_id, job_type, payload, status, attempts, max_attempts, locked_by, locked_at, progress, result, error, created_user, created_at, updated_at)。
- DEV 現況:sbom_scan done 32/failed 6、vulndb_apply done 2/failed 7、evidence_classification done 8、vulndb_check_update done 2。
分工原則(已成文)
- 長工作(幾分鐘以上、不能被 API 重啟打斷)走
background_jobs,禁止自起 Thread 或另開佇列表(docs/claude/domain-capabilities.md:60)。
- 先例:APScheduler 只負責定時丟單、worker 執行(
core/scheduler.py:597-598、core/plugins/_source_scan_vulndb_fetch.py:127-143);排程裡入列要先用 tenant_context(ROOT) 補身分。
- 每小時以內的先例:5 秒、15 分鐘;15 分鐘那支有註解說明為何用排程而非查詢時順手處理(447-457)。
時區
- scheduler 預設 UTC(158),cron 只有一支個別指定 Asia/Taipei(301)。
common/util/common_util.py:36 寫死 BUSINESS_TZ = ZoneInfo("Asia/Taipei"),business_now()(39-50)。
- DEV
users 表沒有語系/時區欄位(只有 email)。
§5
④ 通知
NotificationService(app/notification/service/notification_service.py:22)
send_mail_notification(to, subject, content, is_html=False)(75-84):依收件人網域挑 ROOT 租戶 SMTP 設定(smtp_config_picker.py:25-34),未啟用回 False 不丟例外,背景可用、不需登入者。
send_discord_notification(message, cfg=None)(86-99)、send_telegram_notification(content, cfg=None)(101-117)。
- 底層都到 jedi-notification
NotificationService(provider, config).send_notification(dto) 回 bool(pkg/jedi-notification/jedi_notification/app/service/notification_service.py:7-17);SMTP 實作 infra/smtp_mail/smtp_mail_adapter.py:27-102,失敗只寫 log。
專案啟動信(app/flow_control/service/project_service.py:642-780)
- 收件人兩批:專案參與者(角色 manager/reviewer/auditor/viewer,667-708);任務指派人按人彙總待辦數(711-746)。email 取自
get_user_by_ids 的 user.email(683)。
- 有收
notified_emails(687,707)但第二批沒拿來去重——同時是參與者與指派人的人會收兩封。
- Discord/Telegram 設定要在主執行緒先讀(661-662);每封各開
threading.Thread 寄(752-787),無重試、回傳值丟掉。
- 語系:
flask_babel.gettext(10)走 select_locale,有 request 用觸發者 Accept-Language、沒 request 用預設 zh_Hant_TW(core/app_factory.py:121-133、config/config.py:176)——語系跟觸發者走,不是收件人;使用者表沒有語系欄位。
- 範本:沒有範本引擎,主旨與內文直接是
.po 字串(config/translations/zh_Hant_TW/LC_MESSAGES/messages.po:106-116,en 同名檔);聊天通道另有通用文案(app/notification/service/chat_notice.py:12-25)。
任務指派/完成現在寄什麼
- 新任務
notify_user_todo_job(workflow_execution_service.py:1240-1300)寄給該 job 的每位指派人,主旨 new_task_notification_subject。觸發點:完成上一步(806)、退回(887)、強制開始(job_force_start_service.py:95)、job_service.py:506。
- 批次完成(
job_batch_complete_service.py:137-203;收件人查詢 infra/readmodel/tasks/job_batch_complete_query.py:186-245)寄專案 manager/reviewer+下一步 PROCESSING 任務指派人;單筆通知以 suppress_notify=True 關掉(99)。
notify_users_batch_assigned(workflow_execution_service.py:1193)無呼叫者,死碼。
- 檢測工具整組完成另有
_notify_group_result(見 ⑤)。
通知設定
- DEV
system_configs:NOTIFY_CONFIG 只有 DISCORD({"secret": "", "enabled": false})與 TELEGRAM({"chat_id": …, "enabled": false})兩個 key;SMTP 組有 * 與 gmail.com。
secret 加密存放(infra/system_config/config_secret_crypto.py:28、infra/notification/tenant_notify_config_reader.py:26-37)。
- 兩種讀法:有登入者
get_channel_config()(無身分直接 raise,notification_service.py:33-50);背景用 read_tenant_notify_config_value(tenant_id, channel)(先例 core/plugins/detection.py:97-108)。
- 沒有「哪些事件要通知誰」或「多久提醒一次」的設定。
寄信紀錄
- DEV 搜表名含 notif/mail/email/message/sent/bulletin/inbox:只有
public.bulletins、public.bulletin_org_units。
- BE 與 jedi-notification grep
notification_log|mail_log|notify_log|notification_history|notified_at:只有 remote_agents.cert_expiry_notified_at 單欄去重戳記(scripts/sql/packages/jedi_remote_agent/007-remote-agent-cert-expiry.sql:12)。jedi-notification 沒有任何資料表。確認:沒有寄信紀錄表。
站內通知
- 沒有收件匣/未讀。最接近的 jedi-bulletin 只有部門層級公告、有上下架時間、無個人已讀(
pkg/jedi-bulletin/jedi_bulletin/infra/models/bulletin.py:39-61)。socketio 只登記問卷 namespace(config/socketio_namespaces.py:41-47)。
§6
⑤ 掃描觸發
檢測工具:第一次掃描怎麼派
- 時機:「開始執行任務」→
start_task_execution 把已指派待辦任務改成執行中,再呼叫 _run_activation_hooks(pkg/jedi-task-platform/.../task_execution_service.py:60-94)。
- 檢測工具申報
_auto_dispatch_first_scans(app/detection_tools/task_type_declaration.py:43,108);每支掛鉤、每筆任務各包 SAVEPOINT(task_execution_service.py:124-133、task_type_declaration.py:85-96),失敗不擋。
- 只派一次:只挑「完全沒有執行紀錄」的任務(
get_detection_jobs_without_execution,task_type_declaration.py:79)。
- 派工:
orchestration.start_execution(87),要求任務在執行中(detection_orchestration_service.py:212);每組指派建一張 agent_tasks(pending)+一筆 detection_executions(_dispatch_one,515-536)。
agent_tasks.scheduled_at
- 空值=立即可領;有值=到時間才可領(
pkg/jedi-remote-agent/.../model/agent_task.py:59-65)。
- 值從指派列
job_execution_detection_tool_agents.scheduled_at 在派工當下複製(detection_orchestration_service.py:512-522;欄位 pkg/jedi-detection/.../job_execution_detection_tool_agent.py:42-44)。未來時間的執行紀錄狀態為 scheduled(533-534)。單組重跑強制立即(512)。
- agent 拉單有過濾:
list_dispatchable_for_agent 只撈 pending 且 scheduled_at IS NULL OR scheduled_at <= now(),用資料庫時鐘(agent_task_repo_impl.py:18-33)。「立即開始」端點改成 now(agent_task_domain_service.py:148-156);逾時判斷跳過未到時的單(detection_orchestration_service.py:1283-1297)。
- DEV:
agent_tasks succeeded 49/failed 25/cancelled 5,只有 1 筆用過 scheduled_at;指派列 21 筆,0 筆設了 scheduled_at。
SBOM 原始碼掃描(伺服器端)
- 分岔:工具
execution_target 是 server 改走伺服器端派工(detection_orchestration_service.py:220、server_side_dispatch.py:65-67),建執行群組與不掛 agent 工單的執行紀錄,再呼叫登記好的處理函式(server_side_dispatch.py:70-111)。
dispatch_sbom_scan(工具代號 sbom-scan)把 execution_uid 寫進 source_scans.detection_execution_uid 並建掃描列(pkg/jedi-source-scan/.../detection_dispatch_handler.py:14-32)→ 排進 background_jobs(source_scan_app_service.py:153)→ BE _run_sbom_scan 執行(core/plugins/source_scan.py:143-147)。
- 伺服器端沒有 scheduled_at 可用(
server_side_dispatch.py:108),背景佇列也沒有延後機制。
- 重新執行沿用上次的源碼包:伺服器端派工沒帶新檔時,取綁定
tool_params 裡上次那份的 uid(pkg/jedi-detection/.../server_side_dispatch.py:118-137),建掃描列後背景工作以該掃描的 input_file_id 重掃(pkg/jedi-source-scan/jedi_source_scan/app/service/source_scan_job_steps.py:102)。任務抽屜對應顯示「已上傳(沿用上次)」(FE src/config/locales/i18n/zh-tw/my-tasks.json:138 source_file_reused)。週期自動掃走同一條路:不帶新檔派工,即沿用上次的 input_file_id。
- 從未上傳過源碼包的任務,首次執行回 400(
detection_dispatch_handler.py:24);檢測任務啟動掛鉤也會走到這條,被 SAVEPOINT 吞成 warning。
list_task_scans 只列清單,靠 task_context.resolve 用 detection_execution_uid 反查任務/專案,失敗就全列(task_scan_app_service.py:28-55);BE 實作 ReadmodelTaskContextResolver(core/plugins/source_scan.py:177)。
「掃完」的事件
- 檢測工具:
AgentTaskService.receive_result → listener on_task_succeeded/on_task_failed(agent_task_service.py:155-169);port IAgentTaskLifecycleListener 可抽換,BE 的 LazyLifecycleListener → DetectionLifecycleListener → on_scan_succeeded/on_scan_failed(core/plugins/remote_agent.py:47-55、infra/remote_agent/adapter/detection_lifecycle_listener.py:24-37)。
- SBOM:
SbomScanJob.run 跑完對有 detection_execution_uid 的掃描呼叫 DetectionResultForwarder.succeed/fail(sbom_scan_job.py:111-128)→ server_side_result.complete/fail(server_side_result.py:28-49)。
- 兩條路匯合在
_close_group_if_terminal(detection_orchestration_service.py:1626-1675):彙總狀態、auto 完成模式下完成任務、_notify_group_result 發整組彙總通知(Email/Telegram/Discord,2076)。
- 完全不看嚴重度,也沒有可外掛的 event/callback;要加門檻只能改這段。
嚴重度欄位
source_scan_findings.severity:String(16) 可空(source_scan_models.py:87),值域 CRITICAL/HIGH/MEDIUM/LOW/NEGLIGIBLE/UNKNOWN,不在清單內存 UNKNOWN(raw_finding.py:15,76-78)。DEV 分布 MEDIUM 403/HIGH 371/CRITICAL 96/LOW 93。掃描摘要 summary.severity 是各工具小寫計數(summary.py:14-21,33)。
- 檢測工具:
detection_executions 沒有等級欄位(DEV 欄位只有 summary JSONB),鍵依工具而異(detection_execution.py:38、detection_orchestration_service.py:2440-2454)。有等級的是基準檔控制項 detection_profile_controls.severity_norm(none/low/medium/high/critical,無 CHECK,detection_profile_control.py:44-51、inspec.py:286-311)——那是基準定義,不是單次掃描結果。DEV detection_executions succeeded 52/failed 23/cancelled 5。
§7
⑥ 資源庫 frequency 欄
DEV 值分布(compliance.module_frames,174 列,其中 155 列 is_delete=1)
- 大小寫混雜(
QUARTERLY)與拼法不一(annual)全在已刪除列;未刪除的 19 列都是小寫標準值+none。
- 同表
last_execute_date/next_execute_date:174 列全為 NULL,ORM 有欄位(infra/module_frame/models/module_frame.py:28-29)、DTO/serializer 有回傳,但 grep 不到任何寫入者。
frequency 欄型別 varchar(255) NOT NULL(model:26)。
寫入點
app/module_frame/service/module_frame_item_service.py:106,221(payload.get("frequency", ""),空字串當預設)。
app/module_frame/service/module_frame_service.py:105(逐欄更新)。
app/module_frame/service/module_frame_import_service.py:176,200,302,337(Excel 匯入,欄名 Frequency;範本定義 app/module_frame/excel_template/sheet_definitions.py:94 必填)。
app/oscal/service/ssp_excel_import_app_service.py:573、app/oscal/service/resource_library_app_service.py:459-477,551-557(資源庫建立,缺值寫 none)、api/oscal/routes/resource_library_route.py:62。
讀取點
- 只有回傳:
resource_library_app_service.py:98、app/module_frame/dto/module_frame_dto.py:16,50、ssp_import_template_app_service.py:1059、api/module_frame/serializers/module_frame.py:67,87,112。
- 套件
pkg/jedi-compliance-audit/.../flow_control_project_mapper.py:87 把它帶成專案的 module_frame_frequency(project_dto.py:76,132),FE grep 不到任何使用者。
- 沒有任何排程、任務生成、提醒邏輯讀它。
FE 寫死的選項
- 六個值 daily/weekly/monthly/quarterly/semi_annually/annually(不含
none)在四處寫死:src/stores/menuStore.js:168-174(fetchFrequencyMenu 是假 fetch)、src/components/grc/ModuleFrameBasicInfoPanel.vue:171-176(預設 annually,51/98)、src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue:1133-1138、src/components/grc/ssp-excel-import/SheetPreviewBasic.vue:53-58。
- DEV
system_menus 沒有 frequency 相關 group。依 CLAUDE.md「下拉選單進 system_menus」鐵則,這四處屬違規寫死。
§8
⑦ 問卷任務
怎麼建
- 型別申報:
survey,有狀態機與 review_required,無啟動掛鉤(pkg/jedi-survey/.../task_type_declaration.py:39,62-71)。
- 路徑一(規劃頁):
job_service 建任務時呼叫 _replace_survey_snapshots、_reconcile_task_surveys(app/flow_control/service/job_service.py:233-237;更新 352,380)。
- 路徑二(BPMN):
actionType=='survey' → process_survey_property+link_task_survey(app/flow_engine/service/workflow_execution_service.py:394,1306-1334),直接引用問卷定義、不建快照。
結構
- 任務掛一筆
job_evidences(type SURVEY),ref_id 指向快照問卷(flow_control_job_repo_impl.py:850-874)。
survey.task_surveys:問卷×設備一筆,即「問卷實例」(survey_handler.py:82-131);DEV 共 5 筆。
- 回覆在
question_answers(以 task_survey_id 關聯)+question_answer_histories(question_answer_service.py:164-224)。
- 規劃頁路徑深度複製:
create_snapshot 複製 survey、頁、題目、翻譯,is_snapshot=1(survey_snapshot_domain_service.py:36-71);BPMN 路徑引用原問卷(task_survey_service.py:177-217)。
怎麼算完成
- 送答案
update_task_survey_answer 寫答案並改 task_surveys.status(question_answer_service.py:114-195);狀態 0 未填/1 編輯中/2 待審/3 補充說明/9 完成(task_survey_code.py:3-8),合法轉換 task_survey_status_transition.py:30-36。
next_status_on_answer_complete(task_type_declaration.py:116-124)定義了填完該進 2 或 9,無呼叫端,目前由前端直接送 status 值。
- 任務層:批次完成檢查該任務所有
task_surveys 最小 status=9(infra/readmodel/tasks/job_batch_complete_query.py:158-174、job_batch_complete_service.py:85)才 complete_job。填完問卷不會自動完成任務。
現況下「重開」各要動什麼(只描述結構)
- 重開任務:workflow 退回把任務改回執行中,並以
batch_set_task_survey_status 把所有問卷實例設成 1,答案保留不清(workflow_execution_service.py:877-881、task_survey_service.py:476-508)。
- 重開單份回覆:狀態機規定 9 不能轉出(
task_survey_status_transition.py:35),只有批次系統路徑與 Excel 匯入能繞過。換問卷是軟刪舊實例、另建新的,舊答案保留(task_survey_service.py:129-153);已有答案的快照不能移除,回 409(survey_handler.py:181-183)。
- 覆核輪會把
task_surveys 與 question_answers 一起複製到新任務,標 inherited_from_round_id(infra/flow_control/repository/reverify_clone_query.py:4-41)——是「帶舊答案的新一份」的現成先例,但綁覆核輪。
§9
首腦要裁的題目
只列問題,不給設計。
- 「重開」的語意:週期到了是在同一筆任務上退回重做(保留歷史在同一列),還是生一筆新任務(每期一筆)?前者沿用退回路徑但歷次結果會疊在同一筆;後者沒有單筆複製方法,要從覆核輪承襲邏輯抽出或另建。
- 新的一期歸屬哪一輪:任務屬於哪一輪記在
workflow_execution_control_mapping.round_id。週期新生的任務算同一輪、還是要有「週期」這個新的歸屬概念?與覆核輪的母輪關係怎麼分?
- 週期設定存在哪一層:任務上沒有欄位、
config_schema 只到租戶層且平台不讀。是任務層新欄位、型別申報層、還是專案層?資源庫 frequency 只當建專案時的預設帶入,還是也要能影響既有專案?
- 完成/到期掛鉤要不要進平台:申報表目前只有啟動掛鉤。週期判斷、到期提醒、完成後排下一期,要不要比照
on_tasks_activated 加申報掛鉤(屬 jedi-task-platform 套件異動),還是在 BE 排程裡直接查?
- 第 5 題作廢,見 ⑤ 掃描觸發 節。
- 伺服器端掃描怎麼排未來時間:檢測工具有
scheduled_at,SBOM 走的伺服器端派工與 background_jobs 都沒有延後欄位。兩種掃描要不要用同一套排程機制?
- 門檻通知的「門檻」用什麼等級:SBOM 有標準六級;檢測工具單次結果沒有等級欄,只有各工具不同鍵的 summary。第一版門檻只做 SBOM,還是要先把檢測結果正規化出等級?
- 門檻通知比對什麼:「掃到 high 以上就通知」是每次都通知、還是只在「新出現」或「比上次多」時通知?(現有
_notify_group_result 每組必發一封、不看內容。)
- 通知紀錄表的範圍:要記的是「哪次掃描、什麼等級、何時通知誰」。只記門檻通知,還是把現有指派/完成/專案啟動信也一起納入?表放 BE 還是 jedi-notification 套件(套件目前沒有任何表,屬套件異動)?
- 收件人語系與時區:使用者沒有語系/時區欄位,排程裡寄信固定 zh_Hant_TW、時區 Asia/Taipei 寫死。第一版接受現況,還是要先補使用者或租戶層級的語系/時區?
- 多副本重複觸發:APScheduler 多個 api 副本之間沒有鎖,現有 job 都靠冪等。週期重開與門檻通知的冪等依據(例如「這一期已開過」「這次掃描已通知過」)要靠什麼欄位?還是第一版只承諾單副本?
- 頻率選項與正規化:FE 四處寫死六個頻率值、
system_menus 無對應 group;同表 last_execute_date/next_execute_date 全 NULL 無人寫。頻率是否進 system_menus、這兩個閒置欄要啟用還是棄用?另外 system_menus 的 TASK_TYPE 五個 key 與申報表三個型別對不上,是否一併釐清?
- 問卷週期重開帶不帶舊答案:退回路徑保留答案、狀態機禁止 9 轉出、覆核輪複製會帶舊答案。週期新一期問卷要空白重填、帶上期答案當預填,還是由設定決定?填完問卷不會自動完成任務這件事要不要一起處理?