---
title: FR-060 檢測基準內容管理 — 全案收尾 SUMMARY
date: 2026-08-03
status: 實作完成，待 push 與上版
---

# FR-060 檢測基準內容管理 — 全案收尾 SUMMARY

> 收尾日期：**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）。

---

## 1. 為什麼做這件事

決策者的原話：

> 「目前只有項目，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 上線才兩天、沒有租戶真的在用。

---

## 2. 行為差異（使用者看得到什麼不一樣）

### 2.1 選基準之前，終於看得到內容

| 情境 | 之前 | 之後 |
|---|---|---|
| 管理頁看一支基準 | 只有名稱、工具、版本號、上傳時間 | 點進去看到**完整控制項清單**（編號／標題／說明／嚴重度／是否人工待判），可搜尋、可篩選 |
| 判斷「這支會驗什麼」 | 無從判斷，只能掃了看報告 | 摘要區直接給**總數／自動檢查數／人工待判數**，選之前就知道覆蓋率 |
| 人工待判項 | 完全不可見，混在報告的 skip 裡 | 清單獨立標示，負責人可**提前分派人工判定**，不必等報告出來 |
| 剛上傳的基準 | — | 上傳／版更後**自動排入解析**，管理頁顯示解析中並輪詢，完成後直接可瀏覽 |
| 外部網址型基準 | — | **一樣看得到內容**（CM-1072）：抽取時由 BE 代為下載（含 SSRF 防護），拿到 bytes 後走與壓縮檔完全相同的解析路徑 |
| 解析失敗 | — | 顯示明確原因並提供**手動重抽** |

### 2.2 基準終於可以改名與分類

| 情境 | 之前 | 之後 |
|---|---|---|
| 改一支基準的名稱 | **改不動**（name 是唯一索引的一部分），唯一途徑是 fork 複製出一支新的 | 直接編輯，版本歷程與既有派工引用全部保留 |
| 改描述 | 同上 | 直接編輯 |
| 找一支基準 | 一長串名字平鋪，靠肉眼掃 | 三軸分類（標的類型／標的產品／基準體系），列表**分成三欄**可各自排序 |
| 任務下拉選基準 | 一整條平鋪清單 | **依標的類型分組**顯示 |
| 分類選項要增修 | — | 存 DB 由 root 維護（**不是 FE 寫死**），有刪除保護：已被引用的分類不可刪 |

### 2.3 內部行為修正（使用者不直接看到，但影響正確性）

- **停用的基準不再派得出去**：拆表前 `is_active` 守門在拆表後會靜默失效（`None is False` 為假），已停用的 profile 照樣派得出去——這是無聲的授權漏洞，不是 500。已修並補迴歸測試。
- **通知信不再退化成 UUID**：派工通知的「掃描設定」欄位原本在拆表後會靜默變成 `profile:<uuid>`，且無任何測試會抓到。已修並補測試。

### 2.4 url 型基準的內容抽取（CM-1072，延伸納入）

url 型原本一律落 `failed`，理由是 FR-059 的 P5「平台完全不經手檔案」。**實測推翻了「做不到」的假設**——CINC 原生就吃 URL（`cinc-auditor json <tarball 網址>` 解得出 59 條），那道限制是**產品決策的範圍問題而非技術限制**：P5 涵蓋的是「掃描路徑不代管私有 repo 憑證」，而抽取是 FR-060 才有的另一個面向，dev-sec 那兩條是完全公開的 URL。

於是抽取時由 BE 代為下載，拿到 bytes 後**完全共用 file 型的既有路徑**。

🔴 **三條不變式（改這塊之前必須先讀懂）**：

1. **掃描路徑完全不動**——agent 仍原樣把 URL 交給 `cinc-auditor exec`，`_load_profile()` / `build_payload()` 未被觸及。**掃描抓的永遠是 URL 上的即時內容，不是平台抽取的快照**
2. **下載內容不落儲存**——不寫 `upload_files`、`source_type` 維持 `url`、`file_id` / `sha256` 維持 `NULL`。存成 file 型快照會破壞雙軌語意，且掃描（agent 拉 URL）與抽取（平台存快照）從此可能對不上，對不上時沒人知道該信哪個
3. **下載必須在 `session_scope()` 之外**——網路 IO 最多等 60 秒，`_prepare()` 對 url 型只回「待下載的網址」，實際下載由 `_run_worker()` 在兩段 session 之間執行

⚠️ **url 型抽出來的是某一刻的快照**（`master` branch 會漂），沒有 `sha256` 當信任根——FE 已在 `ControlExtractionSummary.vue` 明示給使用者。偏離問題見 §9 follow-up 第 4 項。

---

## 3. commits 清單

### BE（`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 未動 |

### FE（`~/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` | — | 手測後：修多列展開時只剩最後一筆有資料 |

---

## 4. 改動範圍

### 資料模型（三支 migration，**只套 DEV**）

| 檔 | 行數 | 內容 |
|---|---:|---|
| `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` 等九張明細表慣例）。

### BE 程式碼

- 資料層三組全數改寫：`infra/detection_tools/{model,mapper,repository}/` 主從各一套 ＋ 分類字典一套
- domain 層：entity／query entity／repository interface／domain service 主從分立，新增 `get_version_with_profile(uid)` 合成 entity
- app 層：`detection_profile_service.py`（原 551 行 profile service 改寫）＋ `detection_profile_taxonomy_service.py` ＋ `detection_profile_extraction_service.py`
- API：`api/detection_tools/routes/detection_profile_route.py` 十一個 Resource（列表／menu／建立／明細／版本／版更／fork／跨工具複製／停用／控制項清單／抽取狀態與重抽）
- 抽取器：`common/util/profile_extractor/base.py`（95 行，註冊表 ＋ 抽象基底）＋ `inspec.py`（439 行）
- url 型代下載：`common/util/safe_http_fetch.py`（347 行，SSRF 五道防護），由 `detection_profile_extraction_service.py` 在兩段 session 之間呼叫（CM-1072）
- error code：`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`

### FE 程式碼

管理頁從單檔 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 對照。

---

## 5. DEV 最終狀態（2026-08-04 協調者實查，含 CM-1072 生效後）

```
主檔               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，不改變此結論）。

---

## 6. Notion 卡片樹

| 層級 | 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 對吧，算是延伸題目…既然要把功能做完整」。

---

## 7. 規範文件清單

| 文件 | 狀態 |
|---|---|
| `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` ＋ 完整五段） | ⏳ 待做 |

---

## 8. 關鍵決策（濃縮自 LOG 四個 block）

| # | 決策 | 被排除的選項與原因 |
|---|---|---|
| **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 |

---

## 9. 已知 follow-up

### 🔴 阻斷級（上版前必處理）

1. **全案未 push**——BE **15 支**未 push commit（含其他線：E2E arc、文件產出泛化 CM-1049~1053／CM-1067）、FE 6 支。
   ⚠️ **push 是全 branch 的事，不是 FR-060 單獨能決定的**，等決策者明示。

2. **STG／POC 完全未部署**——三支 migration 只套 DEV（收尾日實查兩環境新表數皆 0）。
   **上版是另一件事，需決策者放行。** `design.md §8.2` 有上版前條件清單：
   - [ ] POC 環境唯讀確認（`config.detection_tool_profiles` 的存在狀態與筆數作為基準）
   - [ ] 三台部署機已安裝 CINC Auditor，`CINC_AUDITOR_CMD` 路徑解析各機實測可用
   - [x] `docs/claude/host-dependencies.md` 已新增第三項（CINC Auditor）— **收尾日實查確認已補**
   - [ ] DEV 驗收（§8.1）全數通過
   - [ ] 🔴 決策者當次明示放行，才依序套 STG → POC
   - [ ] 上版後 POC 既有掃描任務仍可正常派工（soft-ref 引用零轉換的實地確認）

   🔴 **派工單或交接文件若寫「三環境都套」，該指令本身可能就是錯的，停下問決策者。**

### 待決 / 待補

3. **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）。

4. 🔴 **url 型明細與來源內容會靜默偏離**——掃描抓的是 URL 上的即時內容，而控制項明細是某次抽取的快照。git 上改了平台不會知道，明細就一直是舊的。目前**沒有任何自動更新機制**，只有兩個抽取時機：建立版本時自動排一次、手動按重抽。

   決策者已裁定方向（**甲案**）：開明細頁時對來源 URL 發輕量請求（HEAD／`If-None-Match`）比對 ETag／Last-Modified，**一樣就直接顯示現有清單（毫秒級），不一樣才顯示「有更新版本，正在重新解析」的讀取畫面並觸發背景重抽**。下次再進來若無變更就直接讀資料。

   ⚠️ **可行性未驗證**：GitHub 的 `archive/refs/heads/master.tar.gz` 是動態產生的，**不一定給穩定的 ETag**，這要實測才知道甲案成不成立。

   被排除的方案：
   - **乙案**（開頁無條件背景重抽）：每次開頁都跑一次 CINC（28～117 秒），被開頁頻率放大，浪費
   - **丙案**（偵測到變更就自動長 v2）：稽核上漂亮，但 url 型會出現同一 URL 有 v1/v2/v3，而掃描永遠拉最新 git 內容 → **任務綁的版本 uid 指不到掃描實際用的東西**，讓「綁版本」這個既有契約自我矛盾

   已另開 Notion 卡追蹤。

5. **13 支基準中 8 支仍 `pending` 未抽取**（另 5 支 succeeded、0 支 failed）。需人工逐一觸發，或評估寫一支批次抽取。

6. **三個欄位的「值」仍是英文**——決策者已知悉，**未決定是否翻譯**：
   - 檢查類型：`kernel_module` 等 14 種
   - 適用角色：`baseline` / `dc` / `dns` / `web`
   - 狀態：`pending-mapping`

7. **一支端點不在任何規格書裡**：`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 時要一併寫這兩支。**

8. **舊表 `config.detection_tool_profiles_deprecated_20260803` 未 DROP**——觀察期後另案處理（`design.md §8.3`）。

---

## 10. 部署 handover

| 項目 | 內容 |
|---|---|
| **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 到同一版號 |

---

## 11. 這一棒真正學到的（詳見 LOG 第 4／5 棒）

0. 🔴 **派工前要查當前 HEAD 有沒有相關異動，不能只憑幾小時前的盤點**——協調者把「url 型無法抽取」當既定事實寫進三份派工單，而該事實在派工當下**已被 commit `25045758` 推翻**。SPEC agent 以程式碼為準沒照抄簡報，是對的判斷——這也印證了**派工單該寫「去讀什麼」而不是「事實是什麼」**。（LOG 第 5 棒）


1. 🔴 **驗收只查 DB 沒起 BE，會漏掉「服務起不起得來」**——CM-1060 完成後 BE 其實是壞的（`__tablename__` 仍指已 rename 的舊表），是 runner 自己在回寫裡揭露的，協調者的驗收沒抓到。**拆表類任務的驗收必須含「服務還活著」。**
2. 🔴 **401 分不出「端點存在」與「端點不存在」**——`@jwt_required()` 在路由解析前就擋下，用 401 驗端點是無效驗證。正解是 **OPTIONS 探路由**（回 200 ＋ `Allow` 標頭）。
3. 🔴 **把共通紀律寫進卡片本身，派工單縮成一行**——決策者原話「不要每次都用攏長 prompt，很浪費 token 也會斷掉」。七張卡各附「必讀清單 ＋ 作業紀律 ＋ 完成後四步」後，派工單只剩「請處理 CM-10xx，照卡做」。
4. **PrimeVue 3 的展開列會整個銷毀重建**——`BodyRow` 用 `<component :is="templates['expansion']">`，而 `templates.expansion` 是父層插槽函式本身，父層每次 re-render 都是新實例 → `:is` 視為新元件型別 → 已展開的列全部銷毀重建。**加 `:key` 或改具名函式 ref 都擋不住**，只能讓子元件自己管載入 ＋ 模組層快取。這是會再遇到的框架坑。
