Repo:BE | 前置:無 | 母卡:CM-1077
母卡 CM-1077 要做的事是「版本與基準的修正與刪除——未被使用過才可改可刪,用過即凍結」。
這件事真正的難點與風險不在動作本身(改名、刪版本、刪主檔都是常規 CRUD),而在判定:
所以拆卡:判定先獨立落地並可被單獨驗證,後續的動作卡(.2 / .3)就只是消費它。判定寫錯是資料安全問題,動作寫錯只是 UI 問題——兩者風險等級不同,不該綁在同一次驗收裡。
BE,唯讀,零破壞性。
🔴 本卡不含任何寫入或刪除動作。 不要順手把 .2 / .3 的刪除端點一起做掉——判定要能被單獨驗證,混進寫入就驗不乾淨了。
① config.job_execution_detection_tools.tool_params->>'profile' = 'profile:<version_uid>'
② compliance.agent_tasks.params->'_profile'->>'uid' = <version_uid>
🔴 只有 source_type='url' 時,這個 uid 才是 profile 的 uid;
file 型存的是 upload_files 的 uid(見下方「相關程式碼」的 build_payload)
③ compliance.detection_executions → agent_task_uid → agent_tasks(判準同 ②)
三個來源代表三個階段:任務綁定(還沒跑)、已派工單、已執行歷史。任何一個階段出現過,就代表這份基準已經進入稽核軌跡。
JOIN compliance.job_executions je ON je.id = jedt.job_execution_id
JOIN compliance.task_assignees ta ON ta.task_id = je.id
JOIN compliance.project_extensions pe ON pe.project_id = ta.project_id
WHERE pe.deleted_at IS NULL為什麼要穿透(決策者親自定的,不可自行簡化):
專案刪除走軟刪(compliance.project_extensions.deleted_at),但全系統 11 處查詢一律把軟刪專案濾掉——沒有任何角色(含 admin)看得到已刪除專案,也沒有任何復原端點。DEV 現況:209 個專案中有 204 個已軟刪。
若照字面判定(不管專案死活,有引用就擋),會出現死結:使用者被一個他看不到、無從查證、也無法處理的專案擋住,且沒有任何自救途徑。
決策者裁示:軟刪專案的引用直接不計入。 核心原則是「擋下的理由必須是使用者看得見的」。這在 GRC 語意上也站得住——專案已刪除,代表那份稽核工作已被放棄。
⚠️ 絕對不可用 projects.status = 'archived' 當軟刪判準。 協調者實查發現:
status 與 deleted_at 是兩套不同步的真相(8 筆已軟刪但 status 仍 in_progress)archived 這個 status 在現行流程中沒有任何寫入端,那 196 筆是歷史遺留唯一可靠判準是 project_extensions.deleted_at IS NOT NULL。
(軟刪專案完全無法檢視或復原這件事本身是個問題,已另開 Notion follow-up 卡記錄,優先權低,不在本卡範圍。)
不穿透的理由:刪主檔會連帶刪掉所有版本與控制項,而任務可能已被刪,但報告與證據還在——那時這支 profile 仍然是稽核軌跡的一部分。母卡原話是「從未產生過任何掃描結果」,主檔的門檻就照這句話走,寧嚴勿寬。
{
in_use: bool,
projects: [{uid, name}],
version_refs: [...]
}
帶專案名稱是為了讓「擋下」的訊息說得出理由(.3 的 tooltip 要用)。
⚠️ projects 只列活著的專案。 軟刪專案既然不參與判定,自然也不該出現在訊息裡——否則就是秀一個使用者查不到的東西給他,比不給理由更糟。
| 版本 | version_uid | 引用數 |
|---|---|---|
| dev-sec Linux 基準 v1 | 42a8368f-3576-4e79-b7e8-8bb004875dd8 |
2 |
| dev-sec Windows 基準 v1 | e43b0535-e137-460a-8cb7-dc319116cab9 |
2 |
| TWGCB-01-011 v1 | ff79e896-0571-4977-9a3b-74c5c6c0ec0b |
1 |
| TWGCB-Windows-2025 v1 | de5087b9-b8e9-4ce1-91cc-9e1dda8abc92 |
1 |
| 其餘 11 個版本 | — | 0 |
四筆引用全部指向專案 301「Agent 測試專用專案」(in_progress、未軟刪) → 上表 4 個版本應判 in_use = true(不可改不可刪),其餘 11 個判 in_use = false(可改可刪)。
原始筆數(供交叉檢查):
job_execution_detection_tools 帶 profile ref:4 筆agent_tasks 帶 _profile:7 筆(其中 url 型 2 筆才指向 profile uid)查 agent_tasks 要用 jsonb 存在運算子:
WHERE params ? '_profile' -- ✅ 正確
WHERE params::text LIKE '%_profile%' -- ❌ 絕對不可_ 在 SQL LIKE 裡是單字元萬用字元,會多撈到一堆無關的列。協調者實測:錯誤寫法回 32 筆,正確寫法只有 7 筆。
(若走 SQLAlchemy,注意 ? 運算子與 text() 的參數綁定衝突——text() 內的 ? / : 都有特殊意義,必要時改用 .op('?') 或 jsonb_exists() 函式形式。)
common/util/detection_profile_ref.py
profile uid 怎麼被寫進派工參數。🔴 必讀 build_payload():
file 型 payload 的 uid 是 upload_files 的 uid、url 型才是 profile 列的 uid
—— 這就是判定規則 ② 那條註記的來源。
extract_profile_uid() 是 'profile:<uid>' 前綴的 canonical 解析,
判定規則 ① 應該 import 它,不要自己寫 startswith("profile:")。
domain/detection_tools/service/detection_profile_domain_service.py
已有 get_by_uid / get_version_by_uid / get_version_with_profile
infra/detection_tools/repository/detection_profile_repo_impl.py
repo 實作
api/detection_tools/routes/detection_profile_route.py
11 個 Resource;URL 註冊在 api/detection_tools/__init__.py
既有可參照的 ref-count pattern(同形狀,優先照抄不要另創):
app/detection_tools/service/detection_tool_service.py:249 count_referencing_tasks()
domain/detection_tools/service/job_execution_detection_tool_domain_service.py:30
api/detection_tools/routes/detection_tool_route.py:155
端點命名慣例:/detection-tools/configs/<uid>/referencing-tasks-count
/detection-profile-taxonomies/<uid>/referencing-profiles-count
⚠️ 端點註冊順序有意義:帶尾綴的路徑必須註冊在 /<string:uid> 之前,否則會被當成 uid 吃掉(Flask 按註冊順序比對)。既有註解就寫在 api/detection_tools/__init__.py 裡。
⚠️ 相關表的 schema 不同,JOIN 要寫對: config.job_execution_detection_tools、compliance.agent_tasks、compliance.detection_executions、compliance.job_executions、compliance.task_assignees、compliance.project_extensions。
@transaction;只有 infra 層能碰 session / ORM。repo 的 session 必須 lazy(繼承 BaseRepositoryImpl,或自己寫 @property session),__init__ 內寫 self.session = get_session() 會直接 500。count_referencing_tasks 這條既有 pattern,優先 reuse / extend。git add <檔名>,禁用 -am(多 session 併發是本專案常態)。jedi_common.handler.exception + GrcErrorCode,不用裸 ValueError。log/app.log 找 stack trace,不要先問 user。