SonarQube Quality Gate — new-code 策略設定紀錄

落地日期:2026-07-25 Server:http://192.168.50.171:9000(SonarQube 26.7.0.124771) 涵蓋專案:guidant-ai-backend / guidant-ai-fe Notion case:CM-891「SonarQube Quality Gate 設 new-code 策略」

1. 背景與動機

Sonar CODE_SMELL 存量 BE 789 / FE 681 條,其中含既有裁決禁區(S3776 認知複雜度 ×241、 S3516 BLOCKER ×8)——靠人力清不完,也不該全清。若 CI 接入時 Quality Gate 沿用 overall(存量)條件,CI 第一天就會被千條存量打紅,團隊會習慣「CI 紅是正常的」, gate 形同虛設。

正確做法:gate 只看 new code(新程式碼零新增 issue 才過),存量當 baseline 不擋版, 日後分批派 runner 清機械性桶(註解碼 / 未用參數等),禁區維持不動。

2. Gate 設定:Guidant-NewCode

自訂 gate,兩專案皆已指派(api/qualitygates/select)。條件只有兩條、全在 new code

# Metric 條件 意義
1 new_violations > 0 即 FAIL 新程式碼上任何新 issue(BUG / VULNERABILITY / CODE_SMELL,含所有 severity)都擋版——零新增量
2 new_security_hotspots_reviewed < 100% 即 FAIL 新程式碼引入的 Security Hotspot 必須全部完成人工審查(標 SAFE 或 FIXED)才放行

刻意不設的條件與理由

  • 不設任何 overall(存量)條件:本 case 核心目的。存量 1,470 條 smell 是 baseline, 不應擋新版發佈;存量歸還走獨立 backlog case(Notion CM 已有 BE/FE 品質債兩條)。
  • 不設 coverage 條件(內建 Sonar way 有 new_coverage < 80% FAIL):
    • BE:pytest 環境目前壞的(有獨立修復 case),無法產生覆蓋率報告 → 設了必紅。
    • FE:無測試覆蓋率上報機制 → 同樣必紅。
    • 補回前提見 §6。
  • 不設 duplication 條件(Sonar way 有 new_duplicated_lines_density > 3%): 本專案大量 DDD 樣板碼(repo / mapper / DTO)天然重複率高,貿然設 3% 會產生 假陽性擋版;先以 new_violations 為主,日後有需要再評估加回。

實作備註:SonarQube 26.x api/qualitygates/create 建立 gate 時會自動帶入 CaYC(Clean as You Code)四條預設條件(new_violations / new_coverage / new_duplicated_lines_density / new_security_hotspots_reviewed), 因此實際操作是建立後用 api/qualitygates/delete_condition 移除 coverage 與 duplication 兩條,保留另外兩條。移除後 gate 會被標 caycStatus: non-compliant, 這只是 Sonar 對「未含 coverage 條件」的標註,不影響 gate 運作。

3. New code 定義:PREVIOUS_VERSION

  • 探索結果:兩專案原本即為全域預設繼承(inherited: true, type: PREVIOUS_VERSION)。
  • 已用 api/new_code_periods/set 對兩專案顯式確認為 PREVIOUS_VERSION (與繼承值相同,Sonar 回報仍為 inherited,行為一致)。
  • 選擇理由(PREVIOUS_VERSION 優於 REFERENCE_BRANCH=main)
    1. 目前兩 repo 的掃描都直接在 main(含本機 working tree)上執行,沒有 branch/PR 分析(Community 版也不支援 branch analysis)——若設 REFERENCE_BRANCH=main,main 自己跟自己比,new code 永遠為空,gate 形同虛設。
    2. PREVIOUS_VERSIONsonar.projectVersion 切分:版本未變時,new code 累積 「自上一版以來」的新增碼,正好對齊本專案「一版一 release」的節奏 (version-bump skill 進版 → 下一次掃描重置 baseline)。
    3. 目前 scanner 未傳 sonar.projectVersion(Sonar 記為 not provided), new code period 起點落在該專案第一次被掃描的分析(BE 2026-07-25T03:27Z / FE 2026-07-25T03:31Z)。建議 CI 接入時在 scanner 參數帶上 -Dsonar.projectVersion=$(pyproject 版號)(FE 用 package.json version), 讓 new code 隨產品版本正確重置。

4. 驗證結果(2026-07-25)

兩 repo 各重跑一次掃描(BE scripts/sonar_scan.sh、FE 根目錄 sonar-scanner, 指令見 docs/security-reports/README.md)後查 api/qualitygates/project_status

專案 Gate 狀態 new_violations 實際值
guidant-ai-backend Guidant-NewCode OK(PASSED) 0
guidant-ai-fe Guidant-NewCode OK(PASSED) 0

存量 smell(BE 789 / FE 681)完全不影響 gate 判定,符合「存量不擋版」目標。

5. CI 端接法(給「資安掃描進 GitLab CI」case)

兩種擇一,推薦方案 A:

方案 A — scanner 內建等待(推薦,最簡單)

sonar-scanner \
  -Dsonar.projectKey=guidant-ai-backend \
  -Dsonar.projectVersion="${PROJECT_VERSION}" \
  -Dsonar.qualitygate.wait=true \
  -Dsonar.qualitygate.timeout=300 \
  ... # 其餘參數同 scripts/sonar_scan.sh

qualitygate.wait=true 會讓 scanner 阻塞等 server 端分析完成,gate FAIL 時 scanner 以非零 exit code 結束 → CI job 直接紅。

方案 B — 掃後輪詢 API(scanner 版本不支援 wait 時的 fallback)

  1. 掃描輸出中取得 ce task id(report-task.txtceTaskId)。
  2. 輪詢 api/ce/task?id=<ceTaskId> 直到 status=SUCCESS
  3. api/qualitygates/project_status?projectKey=<key>projectStatus.status != "OK"exit 1

注意事項:

  • CI 用的 token 建議另發專用 analysis token,不共用個人 user token。
  • 本機腳本 scripts/sonar_scan.sh 未加 qualitygate.wait,維持「掃完即走」, gate 判定留給 CI;本機要看結果可查 dashboard 或 project_status API。

6. 日後補 coverage 條件的前提

new_coverage(建議門檻 80%,對齊 Sonar way)前,須先完成:

  1. BE:pytest 環境修復 case 完成(目前跑不動),且 CI 內以 pytest --cov --cov-report=xml 產出 coverage.xml,scanner 加 -Dsonar.python.coverage.reportPaths=coverage.xml
  2. FE:建立測試覆蓋率上報(vitest/jest --coverage 產 lcov),scanner 加 -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
  3. 上述任一 repo 就緒即可先對該 repo 加條件——但 gate 是兩專案共用的 Guidant-NewCode,若只有一邊就緒,需拆成兩個 gate(如 Guidant-NewCode-BE / -FE)或等兩邊都就緒再統一加,屆時再裁決。

7. 操作留痕(API 呼叫摘要)

動作 API 結果
建 gate POST api/qualitygates/create name=Guidant-NewCode 建立成功(自動含 CaYC 四條件)
刪 coverage 條件 POST api/qualitygates/delete_condition 204
刪 duplication 條件 POST api/qualitygates/delete_condition 204
BE 指派 gate POST api/qualitygates/select projectKey=guidant-ai-backend 204
FE 指派 gate POST api/qualitygates/select projectKey=guidant-ai-fe 204
new code period POST api/new_code_periods/set type=PREVIOUS_VERSION(兩專案) 200
驗證 GET api/qualitygates/project_status(兩專案) 皆 OK