FR-056 實作計畫
← 討論稿
概覽討論稿FR-056.1 工具管理 config schemaFR-056.2 任務類型 + 參數FR-056.3 Agent 派工 + executorFR-056.4 執行編排 + 證據 + 通知

FR-056.2 任務類型「檢測工具執行」+ 參數 Implementation Plan

For agentic workers / runner: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans. Steps use - [ ] checkbox syntax.

依賴:FR-056.1(需 detection_tools 目錄 API)。可與 FR-056.3 並行。 母案設計見 design.md,本階段設計見 design §8 的 FR-056.2 段。

Goal: 任務設置頁新增第三種任務類型「檢測工具執行」,選定後可綁定「用哪個檢測工具 + 掃描參數 + 完成模式」,且同步寫回範本 BPMN userTask。此階段只做設定期(存得下、讀得出、重開還在),實際「執行」在 FR-056.4 才接。

Architecture: 沿用既有問卷(survey)任務的三段式:(1) GrcJobTypedetection_tool 值;(2) BPMN userTask 同步——job_type 寫進 actionType、工具選擇寫進 actionInfo、參數與完成模式各寫一個新 camunda:property;(3) 綁定資料落一張簡單的單列 side-table config.job_execution_detection_tools(mirror TaskSurvey 的形狀,但不做 survey 的 snapshot + device×department 笛卡兒積 fan-out——一個任務一個工具一組參數,過度工程無必要)。FE 任務設置頁加第三個 job type 選項 + 工具選擇 + 動態參數表單 + 完成模式選擇。

Tech Stack: 同 FR-056.1(BE DDD / marshmallow / pytest;FE Vue3 / PrimeVue)。


前置:關鍵決策與發現(runner 動工前必讀)

項目 定案 來源/理由
GrcJobType 新值 DETECTION_TOOL = "detection_tool" 明確語意;不複用 ActionType.TOOL_EXECUTION(見下)
ActionType.TOOL_EXECUTION 不直接複用其值 "tool_execution"——它是 dormant placeholder(零使用),但 GrcJobType 與 ActionType 是兩個獨立 enum、不自動同步 Explore:兩 enum 非同步;用 detection_tool 語意更準
綁定資料表 新建 config.job_execution_detection_tools(單列/任務),不走 survey 的 snapshot + 笛卡兒積 survey 機制對「單工具+參數+flag」過重,比例不當
BPMN property actionType=job_type、actionInfo=tool_uid、新增 toolParams(JSON-encoded)、completionMode mirror survey 的 actionInfo + sp_properties JSON-property pattern
完成模式預設 manual(系統掃完不自動按完成,任務留 PROCESSING 等人工),另一值 auto(自動 complete_job) design D8
完成模式不是狀態 completion_mode 只是存在綁定表的 flag,供 FR-056.4 決定掃完要不要自動呼叫 complete_job。不新增 JobStatus、不動 jedi_flow_engine。本階段只存這個值,不做任何狀態邏輯 user 澄清 2026-07-26
org_unit 不涉及(此階段純任務綁定)

兩陷阱同 FR-056.1: QueryEntity 全 Optional=None(幽靈 WHERE)、Entity 可更新欄位預設 None(幽靈覆寫)。

BPMN 同步的既有紀律(務必守): _sync_task_to_template_xml()actionInfo 是 overloaded 欄位——survey 任務存 survey uid,且程式碼註解明言「非 survey 任務的 actionInfo 保留給工具選擇」。detection_tool 正是那個「工具選擇」用途。傳值時只在 job_type == "detection_tool" 時帶 tool_action_info,避免誤蓋別型任務的 actionInfo。


Task 分佈(對應 design T-2.x)

Task 對應 產出 依賴
Task 1 T-2.1 GrcJobType 加值 + BPMN userTask 同步線擴充 FR-056.1 完成
Task 2 T-2.2 job_execution_detection_tools 綁定表 + param_schemas 讀取 + 完成模式,全 DDD 層 Task 1
Task 3 T-2.3 FE 任務設置頁加 detection_tool 類型 + 工具/參數/完成模式 UI Task 2

Task 1(T-2.1): GrcJobType 加值 + BPMN userTask 同步線

Files: - Modify: common/enum/grc_job_type_enum.py - Modify: infra/grc/repository/grc_job_repo_impl.py_sync_task_to_template_xml + caller update_job) - Modify: api/grc/serializers/job.py(兩個 validate.OneOf 自動涵蓋,但要加 detection 專屬欄位) - Test: test/test_grc_job_detection_type.py

common/enum/grc_job_type_enum.py

class GrcJobType(StrEnum):
    GENERAL = "general"
    SURVEY = "survey"
    DETECTION_TOOL = "detection_tool"   # FR-056.2

加值後這些 call site 自動涵蓋(都用 [e.value for e in GrcJobType]_value2member_map_,非硬編碼二值清單):api/grc/serializers/job.py:142,177(OneOf)、app/grc/dto/job_dto.py:176,207(from_view_row)、infra/grc/repository/grc_job_repo_impl.py:200,236唯一要人工處理infra/grc/mapper/grc_job_mapper.py:24to_entitySURVEY if has_surveys else GENERAL 啟發式——這是「從資料反推 job_type」的舊邏輯,detection_tool 的反推放到 Task 2(有綁定表後才能判斷),本 Task 先不動 mapper 啟發式。

test/test_grc_job_detection_type.py:驗證 _sync_task_to_template_xml 在 job_type=detection_tool 時,把 tool_uid 寫進 actionInfo、params 寫進 toolParams property。用既有 grc job repo test 的 XML 斷言慣例(runner 先看 test/ 下既有 _sync_task_to_template_xml 相關 test 怎麼組 template xml fixture)。

infra/grc/repository/grc_job_repo_impl.pyfield_map 建構處,加 detection tool 專屬 property(mirror survey_action_info 的條件寫入):

def _sync_task_to_template_xml(self, job, name, description, guide, job_type,
                               survey_action_info=None,
                               tool_action_info=None,      # detection tool_uid
                               tool_params=None,           # dict → JSON-encode
                               completion_mode=None):
    ...
    if job_type is not None: field_map["actionType"] = job_type or "general"
    if survey_action_info is not None: field_map["actionInfo"] = survey_action_info
    if tool_action_info is not None: field_map["actionInfo"] = tool_action_info   # 工具型任務借用 actionInfo
    if tool_params is not None: field_map["toolParams"] = json.dumps(tool_params, ensure_ascii=False)
    if completion_mode is not None: field_map["completionMode"] = completion_mode

注意:actionInfo 被 survey 與 tool 共用,但同一任務只會是其中一型,不衝突。caller 必須確保只在對應 job_type 時帶對應參數。

update_job(~line 487)計算 survey_action_info 的相鄰處,加 detection 分支:

tool_action_info = None
tool_params = None
completion_mode = None
if job_type == GrcJobType.DETECTION_TOOL.value:
    tool_action_info = (tool or {}).get("uid", "")   # tool 為新傳入參數
    tool_params = (tool or {}).get("params")
    completion_mode = (tool or {}).get("completion_mode")
self._sync_task_to_template_xml(job, name, description, guide, job_type,
                                survey_action_info=survey_action_info,
                                tool_action_info=tool_action_info,
                                tool_params=tool_params,
                                completion_mode=completion_mode)
git add common/enum/grc_job_type_enum.py infra/grc/repository/grc_job_repo_impl.py test/test_grc_job_detection_type.py
git commit -m "feat(fr056): add detection_tool job type + BPMN sync (T-2.1)

GrcJobType 加 detection_tool;_sync_task_to_template_xml 支援 tool_uid(actionInfo)/toolParams/completionMode 寫回 BPMN userTask。"

Task 2(T-2.2): 綁定表 + 參數 schema 讀取 + 完成模式(DDD 全層)

說明:config.job_execution_detection_tools 單列綁定表(DDD 九層,複製 remote_agent 骨架)+ migration。app service 在 job create/update 時,若 job_type=detection_tool,reconcile 這張綁定表(mirror _reconcile_task_surveys 但簡化成單列 upsert)。另加 param_schemas 唯讀讀取(供 FE 拉某工具的參數欄位定義)。

Files: - Create: scripts/sql/2026-07-26-fr056-2-job-execution-detection-tools.sql - Create: infra/domain/app 全層 job_execution_detection_tool 模組檔(同 FR-056.1 附錄 A 骨架) - Create: domain/detection_tools/service/detection_tool_param_schema_domain_service.py(param_schemas 唯讀) - Modify: app/grc/service/job_service.py(加 _replace_detection_tool_binding) - Modify: api/grc/serializers/job.py(加 tool 欄位) - Modify: api/detection_tools/routes/detection_tool_route.py(加 GET param-schema endpoint) - Test: test/test_job_detection_tool_binding.py

scripts/sql/2026-07-26-fr056-2-job-execution-detection-tools.sql(照 sql-migration 慣例,GRANT/sequence/schema_migrations 齊):

-- Date: 2026-07-26
-- FR-056.2 任務↔檢測工具綁定(單列/任務,非 survey 的笛卡兒積)
CREATE TABLE config.job_execution_detection_tools (
    id                 BIGSERIAL PRIMARY KEY,
    uid                VARCHAR(36)  NOT NULL UNIQUE,
    tenant_id          BIGINT       NOT NULL,
    job_execution_id   BIGINT       NOT NULL,             -- soft-ref → compliance.job_executions.id
    detection_tool_id  BIGINT       NOT NULL,             -- FK → config.detection_tools.id(同 schema)
    tenant_config_id   BIGINT,                            -- soft-ref → config.tenant_detection_tool_configs.id(用哪份租戶設定)
    tool_params        JSONB        NOT NULL DEFAULT '{}'::jsonb,
    completion_mode    VARCHAR(20)  NOT NULL DEFAULT 'manual',  -- auto / manual
    is_delete          BOOLEAN      NOT NULL DEFAULT FALSE,
    created_user       VARCHAR(255), updated_user VARCHAR(255),
    created_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT fk_jedt_detection_tool FOREIGN KEY (detection_tool_id) REFERENCES config.detection_tools (id),
    CONSTRAINT uq_jedt_job UNIQUE (job_execution_id)      -- 一任務一綁定
);
COMMENT ON TABLE config.job_execution_detection_tools IS 'FR-056.2 任務↔檢測工具綁定(工具+參數快照+完成模式,單列/任務)';
GRANT SELECT, INSERT, UPDATE, DELETE ON config.job_execution_detection_tools TO cm_app;
GRANT USAGE, SELECT ON SEQUENCE config.job_execution_detection_tools_id_seq TO cm_app;
ALTER TABLE config.job_execution_detection_tools ENABLE ROW LEVEL SECURITY;
CREATE POLICY jedt_tenant_isolation ON config.job_execution_detection_tools
    USING (current_setting('app.is_super_admin', TRUE) = 'true'
           OR tenant_id = ANY (string_to_array(current_setting('app.allowed_tenant_paths', TRUE), ',')::BIGINT[]));
INSERT INTO public.schema_migrations(filename, note) VALUES
  ('2026-07-26-fr056-2-job-execution-detection-tools.sql', 'FR-056.2 任務檢測工具綁定表') ON CONFLICT (filename) DO NOTHING;

套進 DEV + 驗證(同 FR-056.1 Task 1 的 psql 驗證方式)。

GET /detection-tools/<tool_uid>/param-schema 回該工具當前生效版本(is_current=True)的 param_schema,供 FR-056.2 FE 拉參數欄位定義。

驗證:(a) job_type=detection_tool + tool payload → 綁定表落一列;(b) 只改別欄位的 partial update 不清空綁定;(c) job_type 改回 general → 綁定標 is_delete。

app/grc/service/job_service.py,mirror _reconcile_task_surveys 但簡化:

def _replace_detection_tool_binding(self, job_uid, tool, curr_user, tenant_id):
    """單列 upsert(非笛卡兒積)。tool=None 且非 detection 型 → 不動;job_type 改離 detection → 標 is_delete。"""
    existing = self._jedt_domain.get_one(job_execution_uid=job_uid)
    if not tool:
        if existing: self._jedt_domain.soft_delete(existing.uid, curr_user)
        return
    if existing:
        self._jedt_domain.update_binding(JobExecutionDetectionToolEntity(
            uid=existing.uid, tool_params=tool.get("params"),
            completion_mode=tool.get("completion_mode"), updated_user=curr_user))
    else:
        self._jedt_domain.create_binding(JobExecutionDetectionToolEntity(
            uid=str(uuid.uuid4()), job_execution_id=..., detection_tool_id=...,
            tool_params=tool.get("params") or {}, completion_mode=tool.get("completion_mode") or "manual",
            tenant_id=tenant_id, created_user=curr_user, updated_user=curr_user))

create_job / update_job 於處理 survey 綁定的相鄰處,加 if tool is not None or job_type == "detection_tool": → 呼叫此方法。

JobCreateRequestSchema / JobUpdateRequestSchema 加:

tool = fields.Nested(DetectionToolBindingSchema, allow_none=True, load_default=None)
# DetectionToolBindingSchema: uid(工具 uid), params(Dict), completion_mode(OneOf['auto','manual'])

route 的 **data splat 自動把 tool 傳到 app service,app service 簽章加 tool=None kwarg。

infra/grc/mapper/grc_job_mapper.pyto_entityhas_surveys 啟發式旁加「有 detection binding → DETECTION_TOOL」判斷(若 mapper 拿得到綁定資訊;拿不到則靠 BPMN actionType 反推,讀路徑見 ssp_control_implementation_service.pyprops.get("actionType"))。

git commit -m "feat(fr056): job-detection-tool binding + param schema read (T-2.2)"

Task 3(T-2.3): FE 任務設置頁加 detection_tool 類型

FE repo。 檔案 src/views/project/TaskSetupView.vue(既有任務設置頁,1391 行)。

{ label: t('lang.task_setup.job_type_detection'), value: 'detection_tool', icon: 'pi pi-shield' }

TaskConfig interface 的 jobType 型別加 'detection_tool',並加 toolUid: string|nulltoolParams: Record<string,any>completionMode: string

tool: task.jobType === 'detection_tool' ? {
  uid: task.toolUid, params: task.toolParams, completion_mode: task.completionMode
} : null,
git commit -m "feat(fr056): add detection_tool job type to task setup page (T-2.3)"

完成後收尾(等 user 下令)


附錄:DDD 九層照抄範本

同 FR-056.1 implementation-plan.md 附錄 A(來源 remote_agent 模組)。綁定表為 tenant-scoped,model 繼承 BaseModel, TenantScopedMixinModel