收尾日期:2026-08-03(決策者手測驗收通過後) 2026-08-04 更正:CM-1072(url 型支援內容抽取)經決策者裁定納入本 arc,§2.4/§3/§5/§6/§9 已依實查更新——原先「url 型無法抽取、
failed是設計內終態」的敘述全數作廢。 本檔由fr060-LOG.md五個 block + 實查濃縮產生,不憑記憶重建。 現況請看同資料夾fr060-STATE.md(living);決策軌跡看fr060-LOG.md(append-only)。
決策者的原話:
「目前只有項目,user 完全不知道這個東西會驗證什麼,我怎麼知道我要選什麼?哪些基準可以參考,我可以先請負責人去判斷、提前先處理,而不是等報告出來才去處理。」
FR-059 做完了「掃描設定檔管理」,但平台把 profile 當成不透明的二進位檔——使用者在管理頁與任務下拉看到的,從頭到尾只有一個名字。
支撐急迫性的實測數字:TWGCB-01-014 Ubuntu 22.04 LTS 共 234 條控制項,其中 199 條是人工待判項(impact 0.0,掃描時直接 skip),實際自動檢查只有 35 條、覆蓋率 15%。使用者選了、掃了、等了一輪,拿到一份 85% 是 skip 的報告才發現這件事——等於白等一輪掃描。
同樣的形態在另外兩支也成立(收尾日實查):
| 基準 | 總控制項 | 人工待判 | 自動檢查 | 自動覆蓋率 |
|---|---|---|---|---|
| TWGCB-01-014 Ubuntu 22.04 LTS v1.2 | 234 | 199 | 35 | 15% |
| TWGCB-01-007 Windows Server 2016 v1.3 | 699 | 159 | 540 | 77% |
| TWGCB-Windows-2025 | 708 | 150 | 558 | 79% |
差距從 15% 到 79%——這正是「選之前必須看得到」的理由:同樣叫「TWGCB 基準」,選錯一支等於整輪掃描交白卷。
為什麼一個瀏覽功能要順帶拆資料表:現行單表用 (detection_tool_id, tenant_id, name) 當唯一索引——拿 name 當身分,這正是「改名改不動、唯一改名途徑是 fork 複製」的根因;而分類三軸(標的類型/標的產品/基準體系)是「這支 profile 是什麼」的屬性,不是「這一版是什麼」的屬性。當時是最便宜的時機:DEV 僅 13 筆、FR-059 上線才兩天、沒有租戶真的在用。
| 情境 | 之前 | 之後 |
|---|---|---|
| 管理頁看一支基準 | 只有名稱、工具、版本號、上傳時間 | 點進去看到完整控制項清單(編號/標題/說明/嚴重度/是否人工待判),可搜尋、可篩選 |
| 判斷「這支會驗什麼」 | 無從判斷,只能掃了看報告 | 摘要區直接給總數/自動檢查數/人工待判數,選之前就知道覆蓋率 |
| 人工待判項 | 完全不可見,混在報告的 skip 裡 | 清單獨立標示,負責人可提前分派人工判定,不必等報告出來 |
| 剛上傳的基準 | — | 上傳/版更後自動排入解析,管理頁顯示解析中並輪詢,完成後直接可瀏覽 |
| 外部網址型基準 | — | 一樣看得到內容(CM-1072):抽取時由 BE 代為下載(含 SSRF 防護),拿到 bytes 後走與壓縮檔完全相同的解析路徑 |
| 解析失敗 | — | 顯示明確原因並提供手動重抽 |
| 情境 | 之前 | 之後 |
|---|---|---|
| 改一支基準的名稱 | 改不動(name 是唯一索引的一部分),唯一途徑是 fork 複製出一支新的 | 直接編輯,版本歷程與既有派工引用全部保留 |
| 改描述 | 同上 | 直接編輯 |
| 找一支基準 | 一長串名字平鋪,靠肉眼掃 | 三軸分類(標的類型/標的產品/基準體系),列表分成三欄可各自排序 |
| 任務下拉選基準 | 一整條平鋪清單 | 依標的類型分組顯示 |
| 分類選項要增修 | — | 存 DB 由 root 維護(不是 FE 寫死),有刪除保護:已被引用的分類不可刪 |
is_active 守門在拆表後會靜默失效(None is False 為假),已停用的 profile 照樣派得出去——這是無聲的授權漏洞,不是 500。已修並補迴歸測試。profile:<uuid>,且無任何測試會抓到。已修並補測試。url 型原本一律落 failed,理由是 FR-059 的 P5「平台完全不經手檔案」。實測推翻了「做不到」的假設——CINC 原生就吃 URL(cinc-auditor json <tarball 網址> 解得出 59 條),那道限制是產品決策的範圍問題而非技術限制:P5 涵蓋的是「掃描路徑不代管私有 repo 憑證」,而抽取是 FR-060 才有的另一個面向,dev-sec 那兩條是完全公開的 URL。
於是抽取時由 BE 代為下載,拿到 bytes 後完全共用 file 型的既有路徑。
🔴 三條不變式(改這塊之前必須先讀懂):
cinc-auditor exec,_load_profile() / build_payload() 未被觸及。掃描抓的永遠是 URL 上的即時內容,不是平台抽取的快照upload_files、source_type 維持 url、file_id / sha256 維持 NULL。存成 file 型快照會破壞雙軌語意,且掃描(agent 拉 URL)與抽取(平台存快照)從此可能對不上,對不上時沒人知道該信哪個session_scope() 之外——網路 IO 最多等 60 秒,_prepare() 對 url 型只回「待下載的網址」,實際下載由 _run_worker() 在兩段 session 之間執行⚠️ url 型抽出來的是某一刻的快照(master branch 會漂),沒有 sha256 當信任根——FE 已在 ControlExtractionSummary.vue 明示給使用者。偏離問題見 §9 follow-up 第 4 項。
feature/e2e-env-build,共 11 支,🔴 全數未 push)| commit | 卡 | 內容 |
|---|---|---|
7e3e7b97 |
— | 設計定案 design.md(D1–D19 全數拍板,1030 行)+ FR 登記表 |
7124a473 |
— | 討論稿補 D19 多工具擴充邊界 |
fce13bcb |
— | 索引頁狀態改「設計定案,待實作」+重生反映 design.md |
7a7a3b41 |
— | 建三張子需求卡 CM-1055~1057 + STATE/LOG 雙檔交接 |
61eeb2a8 |
— | 建卡收口 + T-1.1 落地後的 STATE/LOG 更新 |
d7cf9abc |
CM-1060 | T-1.1 資料模型:三表 DDL + RLS 八段 + 13 筆搬遷 + 舊表 rename(662 行 SQL,零 Python) |
01d0909b |
CM-1061 | T-1.2+1.3 資料層三組(model/mapper/repo)+ domain/app service + 讀取端 API(38 檔,+2925/−1589) |
40fcc38d |
CM-1062 | T-1.4 派工鏈兩處破口改走 get_version_with_profile() + 迴歸測試(2 檔,+137/−7) |
a2f2512b |
CM-1063 | T-2.1+2.2 分類 enum 表 + seed + 13 筆補登 + PUT 編輯端點 + 刪除保護(22 檔,+2028) |
ec4e70b7 |
CM-1065 | T-3.2+3.3 抽取器註冊表 + InSpec extractor + 非同步抽取管線(6 檔,+1170/−6) |
25045758 |
CM-1072 | url 型基準支援內容抽取(BE 代下載 + SSRF 防護):新增 common/util/safe_http_fetch.py(347 行,五道防護)、改抽取服務(+120)/序列化(+4)/design.md(+28)。零 SQL migration,STG/POC 未動 |
~/Projects/Billows/Audit-Manager/compliance-manager-fe/,同 branch,🔴 全數未 push)| commit | 卡 | 內容 |
|---|---|---|
28f061f |
CM-1063 | 補 8 個 detection-tools error code i18n 對照(2 檔) |
97f9ac8 |
CM-1064 | T-2.3+2.4 管理頁重構(1,239→639 行,拆六支子元件)+ 下拉分組 + fallback 攤平(15 檔,+1912/−868) |
bbcb882 |
CM-1066 | T-3.4 控制項詳細頁 + 摘要區 + 抽取狀態輪詢(12 檔,+1136/−18) |
07b8a7d |
— | 手測後:列表分類欄拆成標的類型/標的產品/基準體系三欄 |
e4e7967 |
— | 手測後:控制項展開列的中繼資料標籤中文化 |
7274d13 |
— | 手測後:修多列展開時只剩最後一筆有資料 |
| 檔 | 行數 | 內容 |
|---|---|---|
scripts/sql/2026-08-03-fr060-1-detection-profile-split.sql |
662 | 單表拆主檔/從檔+控制項表;RLS 主從各 4 段共 8 段;13 筆資料搬遷;舊表 rename 為 detection_tool_profiles_deprecated_20260803(未 DROP) |
scripts/sql/2026-08-03-fr060-2-detection-profile-taxonomies.sql |
272 | 分類字典表 config.detection_profile_taxonomies(兩軸 CHECK + (axis,key) 唯一 + slug 格式檢查)+ seed + 13 筆既有資料補登分類 |
scripts/sql/2026-08-03-fr060-3-param-schema-extraction-declaration.sql |
91 | 參數 schema + 抽取宣告欄位 |
新表:
config.detection_profiles(主檔,17 欄,含 target_type / target_product / benchmark_family 三軸)config.detection_profile_versions(從檔,含 extraction_status / extraction_error)config.detection_profile_controls(控制項,17 欄,含 severity_raw / severity_norm / is_pending / attributes jsonb)config.detection_profile_taxonomies(分類字典,兩軸)控制項表刻意不掛 RLS(判準「不會被當獨立查詢入口」,比照 oscal.catalog_controls 等九張明細表慣例)。
infra/detection_tools/{model,mapper,repository}/ 主從各一套 + 分類字典一套get_version_with_profile(uid) 合成 entitydetection_profile_service.py(原 551 行 profile service 改寫)+ detection_profile_taxonomy_service.py + detection_profile_extraction_service.pyapi/detection_tools/routes/detection_profile_route.py 十一個 Resource(列表/menu/建立/明細/版本/版更/fork/跨工具複製/停用/控制項清單/抽取狀態與重抽)common/util/profile_extractor/base.py(95 行,註冊表 + 抽象基底)+ inspec.py(439 行)common/util/safe_http_fetch.py(347 行,SSRF 五道防護),由 detection_profile_extraction_service.py 在兩段 session 之間呼叫(CM-1072)common/code/detection_tools_error_code.py 新增 10 條app/detection_tools/service/detection_orchestration_service.py 兩處呼叫點改走 get_version_with_profile()test/test_detection_profile_version_alloc.py(重寫 +334/−…)、test_detection_profile_dispatch.py(+133)、test_detection_profile_taxonomy.py(340 行,新增)、test_detection_profile_archive.py管理頁從單檔 1,239 行拆成一頁 + 六支子元件:
src/views/detection-profile/
├── DetectionProfileManageView.vue 688 行(原 1,239)
├── DetectionProfileControlsView.vue 632 行(新,控制項詳細頁)
├── profileDisplay.js 顯示名解析共用
└── components/
├── ProfileFormDialog.vue 433
├── ControlExtractionSummary.vue 279
├── ProfileVersionTable.vue 249
├── ProfileDetailDialog.vue 170
├── ProfileNewVersionDialog.vue 175
└── ProfileCopyDialog.vue 149
另動 src/components/detection-tools/DetectionConfigField.vue(下拉分組)、src/config/api/api.js(新增 controls / extraction / taxonomies 端點常數)、i18n error code 對照。
主檔 13
版本 13
控制項 2,131
分類字典 13(target_type 8 + benchmark_family 5)
抽取 succeeded 5
抽取 failed 0
抽取 pending 8
⚠️ 本段數字已兩度修正: ① 協調者交付的事實基準寫「控制項 942/已抽取 2 支」,收尾日(08-03)實查為「1,641/succeeded 3/failed 1/pending 9」,差異來自手測期間又觸發了一支抽取(
TWGCB-Windows-2025,708 條)。 ② CM-1072(25045758)讓 url 型可抽取後,08-04 實查為 2,131/succeeded 5/failed 0/pending 8——原本那支failed的 dev-sec 基準已抽取成功,failed歸零。 三環境未動的結論兩次都不變。
🔴 原本記在此處的「url 型無法抽取、failed 是設計內終態」已被推翻,正確行為見 §2.4。CM-1072 之前的錯誤訊息「平台不代為下載…請改以上傳壓縮檔的方式建立版本」已不再出現。
STG/POC 完全未動(config.detection_profile% 新表數皆為 0;CM-1072 零 SQL migration,不改變此結論)。
| 層級 | Case No | 狀態 |
|---|---|---|
| 母案 FR-060 | CM-1054 | In progress |
| FR-060.1 資料模型重構 | CM-1055 | 子需求卡 |
| FR-060.2 分類體系+編輯 | CM-1056 | 子需求卡 |
| FR-060.3 抽取+瀏覽 | CM-1057 | 子需求卡 |
| T-1.1 拆表 migration | CM-1060 | 修正待驗證 |
| T-1.2+1.3 資料層/服務層 | CM-1061 | 修正待驗證 |
| T-1.4 派工鏈破口 | CM-1062 | 修正待驗證 |
| T-2.1+2.2 分類 BE | CM-1063 | 修正待驗證 |
| T-2.3+2.4 管理頁 FE | CM-1064 | 修正待驗證 |
| T-3.2+3.3 抽取 BE | CM-1065 | 修正待驗證 |
| T-3.4 詳細頁 FE | CM-1066 | 尚未回寫 |
| url 型支援內容抽取(延伸) | CM-1072 | 狀態待標記 |
子任務卡採乙案共 7 張(CM-1060~1066),與執行 session 一對一——非原規劃的 12 張(理由見 §8)。原 T-3.1(裝 CINC Auditor)未單獨建卡,安裝屬環境動作由決策者決定時機,程式碼與文件部分併入 CM-1065。
🔴 CM-1072 原被劃在 FR-060 之外,2026-08-04 由決策者裁定納入本 arc——它改的就是 FR-060.3 的抽取服務、程式碼已在裡面、DEV 已生效。決策者原話:「那看起來 1072 必須納入 fr-60 對吧,算是延伸題目…既然要把功能做完整」。
| 文件 | 狀態 |
|---|---|
design.md(1030 行,D1–D19) |
✅ 已 commit(7e3e7b97) |
discussion.md / discussion.html(討論稿,18 項 D 項 callout + 4 張 mermaid) |
✅ 已 commit |
README.md / README.html(需求索引頁) |
✅ 已 commit |
fr060-STATE.md(living 交接檔) |
✅ 收尾日已更新 |
fr060-LOG.md(append-only) |
✅ 已追加第 5 棒 block(CM-1072 納入 + 收尾文件更正) |
docs/claude/host-dependencies.md 第三項 CINC Auditor |
✅ 已補(ec4e70b7,含授權紅線/路徑解析順序/缺了會怎樣/各主機狀況表) |
docs/specs/current/system-admin/detection-profile-manage.md |
✅ 已由另一支 agent 更新,內容以程式碼為準(含 CM-1072 的 url 型抽取) |
docs/user-manual/scan-profile-guide.md |
⏳ 另一支 agent 同步進行中 |
Notion 母卡 CM-1054 收口(狀態 Done + 完整五段) |
⏳ 待做 |
| # | 決策 | 被排除的選項與原因 |
|---|---|---|
| D5 | 分類 enum 值存 DB 由 root 維護 | 被排除:FE 寫死。決策者當場推翻原建議,原話「這類最好都要可以維護」。連帶多出三塊必做工作(顯示名不落庫/刪除保護/維護介面),FR-060.2 範圍因此比討論稿估的肥 |
| D19 | 多工具擴充採三層結構,擴充點一律「加資料/加實作類別」 | 被排除:加欄位/改欄位語意。決策者原話「不能像這次一樣又大幅度更新,上線後很危險」。連帶改寫 D12 欄位定義(嚴重度欄不叫 impact,用 severity_raw + severity_norm) |
| D20 | 名稱與描述多語化砍出本期範圍 | 決策者:「我不想把需求拉複雜,名稱跟描述之後再補吧」。記在 design.md §6.3 免得半年後有人重新發現一次 |
| — | 子任務卡改乙案 7 張(卡與執行 session 一對一) | 被排除:第 2 棒規劃的 12 張。原 T 之間依賴過緊(T-1.2 與 T-1.3 必定同一 session 做完),卡切得比執行單位細只會讓回寫時出現「一張卡改一半」 |
| — | DEV 階段不加相容 view | 拆表後 BE 會有一段壞掉的過渡期。決策者裁示「開發階段就是開發,直接往下做」 |
| — | 人工待判判準用 impact == 0.0 |
被排除:tags.check_type == 'pending'——會漏 6 條(twgcb_01_014_0079~0084 標 check_type: 'service' 但實為 impact 0.0 且只有 skip)。impact 是 InSpec 原生語意,外來 profile 也一定有 |
| — | 控制項路徑前綴用 **-versions |
控制項屬於某一版而非一支基準(同支 v1/v2 內容可以完全不同),uid 收的是從檔** uid |
全案未 push——BE 15 支未 push commit(含其他線:E2E arc、文件產出泛化 CM-1049~1053/CM-1067)、FE 6 支。 ⚠️ push 是全 branch 的事,不是 FR-060 單獨能決定的,等決策者明示。
STG/POC 完全未部署——三支 migration 只套 DEV(收尾日實查兩環境新表數皆 0)。 上版是另一件事,需決策者放行。 design.md §8.2 有上版前條件清單:
🔴 派工單或交接文件若寫「三環境都套」,該指令本身可能就是錯的,停下問決策者。
CINC Auditor 部署機安裝未確認——本機 mac 已裝 7.1.7(/usr/local/bin/cinc-auditor)。host-dependencies.md 的主機狀況表顯示三台部署機(188/189/190)皆為「未確認」,欄位註記「安裝等決策者放行」。上版前必須逐台裝好並實測。 ⚖️ 授權紅線:必須是 CINC Auditor(Apache 2.0),絕不可換官方 InSpec 6+ 商業 binary 或 inspec-core gem(均受 Chef EULA)。
🔴 url 型明細與來源內容會靜默偏離——掃描抓的是 URL 上的即時內容,而控制項明細是某次抽取的快照。git 上改了平台不會知道,明細就一直是舊的。目前沒有任何自動更新機制,只有兩個抽取時機:建立版本時自動排一次、手動按重抽。
決策者已裁定方向(甲案):開明細頁時對來源 URL 發輕量請求(HEAD/If-None-Match)比對 ETag/Last-Modified,一樣就直接顯示現有清單(毫秒級),不一樣才顯示「有更新版本,正在重新解析」的讀取畫面並觸發背景重抽。下次再進來若無變更就直接讀資料。
⚠️ 可行性未驗證:GitHub 的 archive/refs/heads/master.tar.gz 是動態產生的,不一定給穩定的 ETag,這要實測才知道甲案成不成立。
被排除的方案:
已另開 Notion 卡追蹤。
13 支基準中 8 支仍 pending 未抽取(另 5 支 succeeded、0 支 failed)。需人工逐一觸發,或評估寫一支批次抽取。
三個欄位的「值」仍是英文——決策者已知悉,未決定是否翻譯:
kernel_module 等 14 種baseline / dc / dns / webpending-mapping一支端點不在任何規格書裡:POST /api/1.0/detection-tool-profile-versions/<uid>/extraction(手動重抽,失敗後的補救途徑),是 a2f2512b(CM-1063)順手帶進來的,FE 已在用(DETECTION_TOOL_PROFILE_VERSION_EXTRACTION)。 同路徑的 GET 是抽取狀態輪詢(建議 5 秒一次)。權限:POST 對 create capability(與版更同一組,兩者都是「產生這一版的內容」),GET 不設 capability 與其他讀取端一致。 補進 spec 時要一併寫這兩支。
舊表 config.detection_tool_profiles_deprecated_20260803 未 DROP——觀察期後另案處理(design.md §8.3)。
| 項目 | 內容 |
|---|---|
| DB 環境 | DEV 192.168.50.188:25432 / guidant_ai_dev;STG 同機 guidant_ai_stg;POC 192.168.50.189:25432 / guidant_ai_poc |
| 帳號 | cmmgr(繞 RLS,跑 migration 用)/ cm_app(受 RLS)。密碼查 BE .env 的 DB_SECRET |
| 套 migration 指令 | psql --single-transaction -v ON_ERROR_STOP=1 -f <檔>,依序 fr060-1 → fr060-2 → fr060-3 |
| 主機層新依賴 | CINC Auditor 7.x(275MB,其中約 200MB 是本案用不到的雲端 SDK)。安裝 curl -fsSL https://omnitruck.cinc.sh/install.sh | bash -s -- -P cinc-auditor -v 7 |
| 新環境變數 | CINC_AUDITOR_CMD(選配)——binary 裝在非標準路徑時指向絕對路徑。不要硬編,解析順序:env var → PATH → /usr/local/bin → /opt/cinc-auditor/bin → /usr/bin |
| 缺 CINC 會怎樣 | 基準上傳/版更仍然成功,派工掃描完全不受影響(agent 吃的是壓縮檔本身);只有內容解析落成 extraction_status = failed,管理頁控制項清單看不到東西 |
| BE 重啟 | 資料層全面改寫,部署後必須重啟 BE |
| FE 版本對齊 | 上版時 FE package.json 需 bump 到同一版號 |
🔴 派工前要查當前 HEAD 有沒有相關異動,不能只憑幾小時前的盤點——協調者把「url 型無法抽取」當既定事實寫進三份派工單,而該事實在派工當下已被 commit 25045758 推翻。SPEC agent 以程式碼為準沒照抄簡報,是對的判斷——這也印證了派工單該寫「去讀什麼」而不是「事實是什麼」。(LOG 第 5 棒)
🔴 驗收只查 DB 沒起 BE,會漏掉「服務起不起得來」——CM-1060 完成後 BE 其實是壞的(__tablename__ 仍指已 rename 的舊表),是 runner 自己在回寫裡揭露的,協調者的驗收沒抓到。拆表類任務的驗收必須含「服務還活著」。
🔴 401 分不出「端點存在」與「端點不存在」——@jwt_required() 在路由解析前就擋下,用 401 驗端點是無效驗證。正解是 OPTIONS 探路由(回 200 + Allow 標頭)。
🔴 把共通紀律寫進卡片本身,派工單縮成一行——決策者原話「不要每次都用攏長 prompt,很浪費 token 也會斷掉」。七張卡各附「必讀清單 + 作業紀律 + 完成後四步」後,派工單只剩「請處理 CM-10xx,照卡做」。
PrimeVue 3 的展開列會整個銷毀重建——BodyRow 用 <component :is="templates['expansion']">,而 templates.expansion 是父層插槽函式本身,父層每次 re-render 都是新實例 → :is 視為新元件型別 → 已展開的列全部銷毀重建。加 :key 或改具名函式 ref 都擋不住,只能讓子元件自己管載入 + 模組層快取。這是會再遇到的框架坑。