<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CI/CD 教學 on Tarragon</title><link>https://tarrragon.github.io/blog/ci/</link><description>Recent content in CI/CD 教學 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 06 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/ci/index.xml" rel="self" type="application/rss+xml"/><item><title>CI/CD 失敗到修復發布流程</title><link>https://tarrragon.github.io/blog/ci/github-actions-failure-flow/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/github-actions-failure-flow/</guid><description>&lt;p>CI/CD 失敗處理的核心責任是把紅燈轉成明確的下一步路由。紅燈本身是驗證或交付層的訊號；工程流程要做的是找出失敗層、重現同一個條件、修正後重新讓 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合如何在合併前自動驗證變更品質與相容性">CI Pipeline&lt;/a> 證明變更可發布。&lt;/p>
&lt;h2 id="失敗後先看什麼">失敗後先看什麼&lt;/h2>
&lt;p>失敗後第一步是定位 workflow 與 job。CI/CD 系統會把一次 push、pull request、tag 或 release 拆成多個 workflow，每個 workflow 下面又有多個 job；真正的下一步取決於是哪一層失敗。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>失敗位置&lt;/th>
 &lt;th>常見原因&lt;/th>
 &lt;th>下一步路由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Lint / format&lt;/td>
 &lt;td>程式碼、文件或設定格式不符&lt;/td>
 &lt;td>回本機跑同一條 lint / format 命令&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Test&lt;/td>
 &lt;td>單元、整合、瀏覽器或裝置測試回歸&lt;/td>
 &lt;td>下載 report，回本機用同條件重現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build&lt;/td>
 &lt;td>編譯、bundle、package 或靜態產物失敗&lt;/td>
 &lt;td>回本機跑 production build 入口&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Package&lt;/td>
 &lt;td>image、app bundle、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact&lt;/a> 產生失敗&lt;/td>
 &lt;td>檢查版本、簽章、registry 或路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deploy&lt;/td>
 &lt;td>hosting、runtime、store 或權限設定&lt;/td>
 &lt;td>先確認 build &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact&lt;/a> 是否已成功&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Lint / format 失敗代表靜態契約沒有通過。常見情境是程式格式、文件格式、型別檢查、schema 或設定規則不符合規範。這類失敗的修復路徑通常很短：讀錯誤訊息、修正來源、必要時跑 formatter，再提交修正。&lt;/p>
&lt;p>Test 失敗代表某個行為或契約沒有符合預期。這類失敗要先看 report、screenshot、trace、device log 或 error context，確認是功能真的回歸、測試假設過期，還是測試環境缺少 production-like artifact。直接改測試前，要先確認測試原本守的是哪個使用者或系統行為。&lt;/p>
&lt;p>Build 失敗代表 pipeline 尚未產生可部署產物。這類失敗通常來自編譯錯誤、bundle 設定、依賴版本、環境變數、template 或資源路徑。修復時以專案定義的 production build 命令作為最小重現入口。&lt;/p>
&lt;p>Deploy 失敗代表發布動作沒有完成。這類失敗需要先區分 artifact 是否存在、發布通道權限是否正確、環境保護是否放行。若測試與 build 已成功，deploy 失敗多半是發布通道問題；若 artifact 沒有產生，應回到 build 或 package 階段。&lt;/p>
&lt;h2 id="本機重現流程">本機重現流程&lt;/h2>
&lt;p>本機重現的責任是讓修復建立在同一個驗證條件上。CI 是用乾淨環境執行的一組命令；只要能在本機跑出同樣的失敗，修復就能被快速驗證。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">make build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">make &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">make deploy-dry-run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Build 命令驗證 production artifact 是否能產生。這一步應該接近 CI 使用的 build 入口，避免開發模式遮蔽 production 問題。&lt;/p>
&lt;p>Test 命令驗證產物或程式行為。前端可能是 browser test，後端可能是 integration / contract test，App 可能是 device test，Docker 可能是 image scan 或 smoke test。&lt;/p>
&lt;p>Deploy dry-run 命令驗證發布前條件。高風險部署至少要能檢查 artifact、權限、環境與版本資訊；沒有 dry-run 的專案，也應保留對等的 preflight check。&lt;/p>
&lt;h2 id="修復與重新觸發">修復與重新觸發&lt;/h2>
&lt;p>修復流程的核心是用新 commit 讓 CI 重新驗證。一般流程不需要刪掉失敗 commit，也不需要 force push；失敗 commit 留在歷史裡，後續 fix commit 會形成清楚的修復脈絡。&lt;/p>
&lt;ol>
&lt;li>讀失敗 job 的 log 或 artifact。&lt;/li>
&lt;li>在本機跑對應命令重現。&lt;/li>
&lt;li>修改最小必要範圍。&lt;/li>
&lt;li>跑同一條本機命令確認修復。&lt;/li>
&lt;li>commit 並 push。&lt;/li>
&lt;li>等 GitHub Actions 重新跑。&lt;/li>
&lt;/ol>
&lt;p>這個流程的好處是保留可追溯性。日後再看到同類失敗，可以從 commit history 與 CI log 找到當時的判讀方式。&lt;/p>
&lt;h2 id="發布-gate-路由">發布 gate 路由&lt;/h2>
&lt;p>發布 gate 的責任是把「是否進入下一階段」變成明確條件。這一頁只處理失敗後的操作路由；&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">required checks&lt;/a>、job &lt;code>needs&lt;/code>、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">artifact handoff&lt;/a> 的設計原理，獨立放在 &lt;a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>CI/CD 失敗處理的核心責任是把紅燈轉成明確的下一步路由。紅燈本身是驗證或交付層的訊號；工程流程要做的是找出失敗層、重現同一個條件、修正後重新讓 <a href="/blog/ci/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合如何在合併前自動驗證變更品質與相容性">CI Pipeline</a> 證明變更可發布。</p>
<h2 id="失敗後先看什麼">失敗後先看什麼</h2>
<p>失敗後第一步是定位 workflow 與 job。CI/CD 系統會把一次 push、pull request、tag 或 release 拆成多個 workflow，每個 workflow 下面又有多個 job；真正的下一步取決於是哪一層失敗。</p>
<table>
  <thead>
      <tr>
          <th>失敗位置</th>
          <th>常見原因</th>
          <th>下一步路由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lint / format</td>
          <td>程式碼、文件或設定格式不符</td>
          <td>回本機跑同一條 lint / format 命令</td>
      </tr>
      <tr>
          <td>Test</td>
          <td>單元、整合、瀏覽器或裝置測試回歸</td>
          <td>下載 report，回本機用同條件重現</td>
      </tr>
      <tr>
          <td>Build</td>
          <td>編譯、bundle、package 或靜態產物失敗</td>
          <td>回本機跑 production build 入口</td>
      </tr>
      <tr>
          <td>Package</td>
          <td>image、app bundle、<a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact</a> 產生失敗</td>
          <td>檢查版本、簽章、registry 或路徑</td>
      </tr>
      <tr>
          <td>Deploy</td>
          <td>hosting、runtime、store 或權限設定</td>
          <td>先確認 build <a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact</a> 是否已成功</td>
      </tr>
  </tbody>
</table>
<p>Lint / format 失敗代表靜態契約沒有通過。常見情境是程式格式、文件格式、型別檢查、schema 或設定規則不符合規範。這類失敗的修復路徑通常很短：讀錯誤訊息、修正來源、必要時跑 formatter，再提交修正。</p>
<p>Test 失敗代表某個行為或契約沒有符合預期。這類失敗要先看 report、screenshot、trace、device log 或 error context，確認是功能真的回歸、測試假設過期，還是測試環境缺少 production-like artifact。直接改測試前，要先確認測試原本守的是哪個使用者或系統行為。</p>
<p>Build 失敗代表 pipeline 尚未產生可部署產物。這類失敗通常來自編譯錯誤、bundle 設定、依賴版本、環境變數、template 或資源路徑。修復時以專案定義的 production build 命令作為最小重現入口。</p>
<p>Deploy 失敗代表發布動作沒有完成。這類失敗需要先區分 artifact 是否存在、發布通道權限是否正確、環境保護是否放行。若測試與 build 已成功，deploy 失敗多半是發布通道問題；若 artifact 沒有產生，應回到 build 或 package 階段。</p>
<h2 id="本機重現流程">本機重現流程</h2>
<p>本機重現的責任是讓修復建立在同一個驗證條件上。CI 是用乾淨環境執行的一組命令；只要能在本機跑出同樣的失敗，修復就能被快速驗證。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">make build
</span></span><span class="line"><span class="ln">2</span><span class="cl">make <span class="nb">test</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">make deploy-dry-run</span></span></code></pre></div><p>Build 命令驗證 production artifact 是否能產生。這一步應該接近 CI 使用的 build 入口，避免開發模式遮蔽 production 問題。</p>
<p>Test 命令驗證產物或程式行為。前端可能是 browser test，後端可能是 integration / contract test，App 可能是 device test，Docker 可能是 image scan 或 smoke test。</p>
<p>Deploy dry-run 命令驗證發布前條件。高風險部署至少要能檢查 artifact、權限、環境與版本資訊；沒有 dry-run 的專案，也應保留對等的 preflight check。</p>
<h2 id="修復與重新觸發">修復與重新觸發</h2>
<p>修復流程的核心是用新 commit 讓 CI 重新驗證。一般流程不需要刪掉失敗 commit，也不需要 force push；失敗 commit 留在歷史裡，後續 fix commit 會形成清楚的修復脈絡。</p>
<ol>
<li>讀失敗 job 的 log 或 artifact。</li>
<li>在本機跑對應命令重現。</li>
<li>修改最小必要範圍。</li>
<li>跑同一條本機命令確認修復。</li>
<li>commit 並 push。</li>
<li>等 GitHub Actions 重新跑。</li>
</ol>
<p>這個流程的好處是保留可追溯性。日後再看到同類失敗，可以從 commit history 與 CI log 找到當時的判讀方式。</p>
<h2 id="發布-gate-路由">發布 gate 路由</h2>
<p>發布 gate 的責任是把「是否進入下一階段」變成明確條件。這一頁只處理失敗後的操作路由；<a href="/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">required checks</a>、job <code>needs</code>、<a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection</a> 與 <a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">artifact handoff</a> 的設計原理，獨立放在 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</p>
<h2 id="常見處理情境">常見處理情境</h2>
<p>CI 失敗但本機通過時，優先檢查環境差異。常見差異包括語言版本、套件管理器版本、缺少子模組、缺少 build artifact、測試依賴未安裝、時區或檔案大小寫差異。這類問題要把版本與建置前置條件寫進 workflow、Makefile 或 script，讓重現條件成為專案的一部分。</p>
<p>測試不穩定時，優先把 <a href="/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky Test</a> 狀態標出來並建立 owner。短期可以隔離或重跑，長期要找到不穩定來源，例如等待條件錯誤、外部網路依賴、時間假設、測試資料不穩或動畫 transition 尚未完成。測試不穩定會降低 gate 信任度，因此它本身就是需要治理的 CI 問題。</p>
<p>Deploy 失敗但測試通過時，優先看 artifact 與權限。若 build output 存在且可下載，問題通常在部署通道、token permission 或 <a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection</a>；若 artifact 缺失，就回到 build job。</p>
<h2 id="反模式與替代做法">反模式與替代做法</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險</th>
          <th>替代做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>看到紅燈直接重跑</td>
          <td>掩蓋 flaky 或環境問題</td>
          <td>先看失敗 log，再決定是否重跑</td>
      </tr>
      <tr>
          <td>用 <code>--no-verify</code> 或跳過 CI</td>
          <td>把局部問題帶進主線</td>
          <td>修掉 gate 或明確記錄例外</td>
      </tr>
      <tr>
          <td>CI 與本機命令不同</td>
          <td>本機通過但 CI 失敗</td>
          <td>把命令收斂到 Makefile / npm script</td>
      </tr>
      <tr>
          <td>測試直接打外部服務</td>
          <td>網路與第三方狀態污染判斷</td>
          <td>使用 fixture、mock 或可控環境</td>
      </tr>
  </tbody>
</table>
<p>反模式的共同問題是讓 CI 失去判讀價值。CI 的目標是讓綠燈代表「這次變更在定義好的條件下可發布」。</p>
<h2 id="最小可用流程">最小可用流程</h2>
<p>最小可用流程是讓每次變更都有同一條路徑。對小型靜態網站或個人 blog，先做到以下四件事，就能形成穩定發布節奏。</p>
<ol>
<li><code>push</code> 或 PR 觸發 lint / test / build。</li>
<li>production build 有單一入口。</li>
<li>測試失敗時保留 artifact 或 report。</li>
<li>deploy 只接受測試與 build 通過後的產物。</li>
</ol>
<p>這套流程建立後，CI 紅燈就會成為清楚的路由訊號：哪一層壞、用哪個命令重現、修完後用哪個 gate 放行。</p>
<p>若變更涉及後端服務，可再對照 backend 知識卡的 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">Runbook</a>、<a href="/blog/backend/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明事故期間如何判斷回滾、回切與暫停變更">Rollback Strategy</a> 與 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release Gate</a> 進一步細化故障處理順序與放行條件。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>需要理解 CI 在可靠性模組的位置：讀 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>。</li>
<li>需要看靜態站部署案例：讀 <a href="../blog-project-deploy/">本 blog 專案部署</a>。</li>
<li>需要理解 CI gate 設計：讀 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
<li>需要理解發布阻擋策略：讀 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate 與變更節奏</a>。</li>
</ul>
]]></content:encoded></item><item><title>CI gate 與 workflow 邊界</title><link>https://tarrragon.github.io/blog/ci/ci-gate-workflow-boundary/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/ci-gate-workflow-boundary/</guid><description>&lt;p>CI gate 的核心責任是把「是否進入下一階段」變成明確條件。測試、建置、發布與人工審核可以分成不同 workflow 或 job，但只要它們共同決定同一次發布，就需要有清楚的 gate 關係。&lt;/p>
&lt;h2 id="gate-形式">Gate 形式&lt;/h2>
&lt;p>Gate 形式要依控制範圍選擇。PR 合併、job 執行順序、production 發布與 artifact 傳遞是四種不同責任，混在一起會讓紅燈的意義變模糊。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Gate 形式&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">Required checks&lt;/a>&lt;/td>
 &lt;td>阻止未通過測試的 commit 合併&lt;/td>
 &lt;td>PR 或 branch protection 顯示必須通過&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Job &lt;code>needs&lt;/code>&lt;/td>
 &lt;td>讓 deploy 等 test / build&lt;/td>
 &lt;td>同一 workflow 內 deploy 依賴前置 job&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">Environment protection&lt;/a>&lt;/td>
 &lt;td>控制 production / target environment 發布&lt;/td>
 &lt;td>部署環境需要審核或 required reviewers&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff&lt;/a>&lt;/td>
 &lt;td>確保測試與發布使用同一份產物&lt;/td>
 &lt;td>test job 產生 artifact，deploy job 使用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">Required checks&lt;/a> 適合保護主線。它讓測試結果成為合併條件，避免紅燈變更進入 &lt;code>main&lt;/code> 或 release branch（backend 延伸見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合流程如何在合併前驗證品質與相容性">CI Pipeline&lt;/a>）。&lt;/p>
&lt;p>Job &lt;code>needs&lt;/code> 適合同一條 workflow 內的發布管線。它讓 &lt;code>deploy&lt;/code> 必須等 &lt;code>test&lt;/code>、&lt;code>build&lt;/code> 或 &lt;code>package&lt;/code> 成功後才執行，避免 deploy job 先於驗證結果流動（platform 延伸見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deployment-contract/" data-link-title="Deployment Contract" data-link-desc="說明服務與部署平台之間的生命週期約定">Deployment Contract&lt;/a>）。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">Environment protection&lt;/a> 適合正式環境。即使 build 與測試通過，production 或其他目標環境仍可要求人工審核、特定分支或特定 reviewer 才能部署（治理延伸見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release Gate&lt;/a>）。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff&lt;/a> 適合避免「測試一份、發布另一份」的漂移。較嚴謹的流程會讓 build job 產生 artifact，test job 驗證這份 artifact，deploy job 發布同一份 artifact（供應鏈延伸見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/artifact-provenance/" data-link-title="Artifact Provenance" data-link-desc="說明交付物的來源、完整性與簽章關聯如何建立信任">Artifact Provenance&lt;/a>）。&lt;/p>
&lt;h2 id="workflow-邊界">Workflow 邊界&lt;/h2>
&lt;p>Workflow 邊界的責任是決定哪些步驟共享同一條執行圖。放在同一條 workflow 裡的 job 可以用 &lt;code>needs&lt;/code> 建立顯式依賴；分散在不同 workflow 裡的流程，通常要靠 branch protection 或 environment protection 建立跨 workflow gate。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>結構&lt;/th>
 &lt;th>適合情境&lt;/th>
 &lt;th>常見風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>單一 workflow 多 job&lt;/td>
 &lt;td>test / build / deploy 緊密相依&lt;/td>
 &lt;td>YAML 變長，但依賴關係清楚&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多 workflow&lt;/td>
 &lt;td>不同觸發條件或責任完全不同&lt;/td>
 &lt;td>跨 workflow gate 要靠 repo 設定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PR workflow + deploy&lt;/td>
 &lt;td>PR 驗證、main 發布分離&lt;/td>
 &lt;td>main push 若缺 required checks 會漏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Artifact pipeline&lt;/td>
 &lt;td>同一份產物要被測試再發布&lt;/td>
 &lt;td>artifact 版本與權限要治理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>多 workflow 的關鍵風險是順序假設。GitHub Actions 的 workflow 彼此獨立；跨 workflow 順序需要靠 repository 設定或 API 顯式串接。&lt;/p></description><content:encoded><![CDATA[<p>CI gate 的核心責任是把「是否進入下一階段」變成明確條件。測試、建置、發布與人工審核可以分成不同 workflow 或 job，但只要它們共同決定同一次發布，就需要有清楚的 gate 關係。</p>
<h2 id="gate-形式">Gate 形式</h2>
<p>Gate 形式要依控制範圍選擇。PR 合併、job 執行順序、production 發布與 artifact 傳遞是四種不同責任，混在一起會讓紅燈的意義變模糊。</p>
<table>
  <thead>
      <tr>
          <th>Gate 形式</th>
          <th>責任</th>
          <th>判讀方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">Required checks</a></td>
          <td>阻止未通過測試的 commit 合併</td>
          <td>PR 或 branch protection 顯示必須通過</td>
      </tr>
      <tr>
          <td>Job <code>needs</code></td>
          <td>讓 deploy 等 test / build</td>
          <td>同一 workflow 內 deploy 依賴前置 job</td>
      </tr>
      <tr>
          <td><a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">Environment protection</a></td>
          <td>控制 production / target environment 發布</td>
          <td>部署環境需要審核或 required reviewers</td>
      </tr>
      <tr>
          <td><a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff</a></td>
          <td>確保測試與發布使用同一份產物</td>
          <td>test job 產生 artifact，deploy job 使用</td>
      </tr>
  </tbody>
</table>
<p><a href="/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">Required checks</a> 適合保護主線。它讓測試結果成為合併條件，避免紅燈變更進入 <code>main</code> 或 release branch（backend 延伸見 <a href="/blog/backend/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合流程如何在合併前驗證品質與相容性">CI Pipeline</a>）。</p>
<p>Job <code>needs</code> 適合同一條 workflow 內的發布管線。它讓 <code>deploy</code> 必須等 <code>test</code>、<code>build</code> 或 <code>package</code> 成功後才執行，避免 deploy job 先於驗證結果流動（platform 延伸見 <a href="/blog/backend/knowledge-cards/deployment-contract/" data-link-title="Deployment Contract" data-link-desc="說明服務與部署平台之間的生命週期約定">Deployment Contract</a>）。</p>
<p><a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">Environment protection</a> 適合正式環境。即使 build 與測試通過，production 或其他目標環境仍可要求人工審核、特定分支或特定 reviewer 才能部署（治理延伸見 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release Gate</a>）。</p>
<p><a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff</a> 適合避免「測試一份、發布另一份」的漂移。較嚴謹的流程會讓 build job 產生 artifact，test job 驗證這份 artifact，deploy job 發布同一份 artifact（供應鏈延伸見 <a href="/blog/backend/knowledge-cards/artifact-provenance/" data-link-title="Artifact Provenance" data-link-desc="說明交付物的來源、完整性與簽章關聯如何建立信任">Artifact Provenance</a>）。</p>
<h2 id="workflow-邊界">Workflow 邊界</h2>
<p>Workflow 邊界的責任是決定哪些步驟共享同一條執行圖。放在同一條 workflow 裡的 job 可以用 <code>needs</code> 建立顯式依賴；分散在不同 workflow 裡的流程，通常要靠 branch protection 或 environment protection 建立跨 workflow gate。</p>
<table>
  <thead>
      <tr>
          <th>結構</th>
          <th>適合情境</th>
          <th>常見風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單一 workflow 多 job</td>
          <td>test / build / deploy 緊密相依</td>
          <td>YAML 變長，但依賴關係清楚</td>
      </tr>
      <tr>
          <td>多 workflow</td>
          <td>不同觸發條件或責任完全不同</td>
          <td>跨 workflow gate 要靠 repo 設定</td>
      </tr>
      <tr>
          <td>PR workflow + deploy</td>
          <td>PR 驗證、main 發布分離</td>
          <td>main push 若缺 required checks 會漏</td>
      </tr>
      <tr>
          <td>Artifact pipeline</td>
          <td>同一份產物要被測試再發布</td>
          <td>artifact 版本與權限要治理</td>
      </tr>
  </tbody>
</table>
<p>多 workflow 的關鍵風險是順序假設。GitHub Actions 的 workflow 彼此獨立；跨 workflow 順序需要靠 repository 設定或 API 顯式串接。</p>
<h2 id="發布阻擋判讀">發布阻擋判讀</h2>
<p>發布阻擋要同時看 YAML 與 GitHub repository 設定。YAML 說明 workflow 或 job 如何執行；跨 workflow 的「測試通過才發布」通常要靠 <a href="/blog/ci/knowledge-cards/branch-protection/" data-link-title="Branch Protection" data-link-desc="說明主線分支如何以規則保護合併與發布前置條件">Branch Protection</a>、required status checks 或 environment protection。</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>只看 YAML 能判斷嗎</th>
          <th>應檢查的位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>deploy 是否等 build</td>
          <td>可以</td>
          <td>同 workflow 的 <code>needs</code></td>
      </tr>
      <tr>
          <td>deploy 是否等另一條 test workflow</td>
          <td>通常要查設定</td>
          <td><a href="/blog/ci/knowledge-cards/branch-protection/" data-link-title="Branch Protection" data-link-desc="說明主線分支如何以規則保護合併與發布前置條件">Branch Protection</a> / <a href="/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">Required Checks</a></td>
      </tr>
      <tr>
          <td>PR 是否必須通過測試才能合併</td>
          <td>需要查 repo 設定</td>
          <td><a href="/blog/ci/knowledge-cards/branch-protection/" data-link-title="Branch Protection" data-link-desc="說明主線分支如何以規則保護合併與發布前置條件">Branch Protection</a></td>
      </tr>
      <tr>
          <td>目標環境是否需要人工審核</td>
          <td>需要查環境設定</td>
          <td>Environment protection</td>
      </tr>
      <tr>
          <td>測試與發布是否同一份 artifact</td>
          <td>可以部分判斷</td>
          <td>workflow artifact upload / download</td>
      </tr>
  </tbody>
</table>
<p>這個判讀順序能避免錯修。若測試紅燈但目標環境仍發布，問題通常在 deploy gate 尚未把測試狀態納入發布條件。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>反模式的共同問題是讓 CI 綠燈與發布安全之間失去因果關係。CI 的目標是讓綠燈代表「這次變更在定義好的條件下可進下一階段」。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險</th>
          <th>替代做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>deploy workflow 不等 test</td>
          <td>測試紅燈仍可能發布</td>
          <td>用 required checks 或 <code>needs</code></td>
      </tr>
      <tr>
          <td>CI 與本機命令不同</td>
          <td>本機通過但 CI 失敗</td>
          <td>把命令收斂到 Makefile / npm script</td>
      </tr>
      <tr>
          <td>測試與發布各自 build</td>
          <td>測試產物與發布產物漂移</td>
          <td>用 artifact handoff</td>
      </tr>
      <tr>
          <td>看到紅燈直接重跑</td>
          <td>掩蓋 flaky 或環境問題</td>
          <td>先看失敗 log，再決定是否重跑</td>
      </tr>
      <tr>
          <td>用 <code>--no-verify</code> 或跳過 CI</td>
          <td>把局部問題帶進主線</td>
          <td>修掉 gate 或明確記錄例外</td>
      </tr>
  </tbody>
</table>
<h2 id="tripwire">Tripwire</h2>
<p>Tripwire 的責任是提示什麼時候 workflow 結構需要重切，讓團隊從局部 patch 回到 gate 設計。</p>
<ul>
<li>測試紅燈仍發布：把 deploy gate 顯式化，使用 required checks 或同 workflow <code>needs</code>。</li>
<li>本機常常重現不出 CI：把命令收斂到 <code>Makefile</code> 或 <code>npm scripts</code>，減少 workflow 專屬命令。</li>
<li>測試常因 artifact 缺失失敗：建立 artifact handoff，讓測試與發布使用同一份產物。</li>
<li>workflow 說明與實作分叉：同步更新 workflow 文件與 YAML，讓維護入口保持可信。</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>CI 紅燈處理流程：讀 <a href="../github-actions-failure-flow/">CI 失敗到修復發布流程</a>。</li>
<li>靜態站部署案例：讀 <a href="../blog-project-deploy/">本 blog 專案部署</a>。</li>
<li>可靠性層的 release gate：讀 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate 與變更節奏</a>。</li>
</ul>
]]></content:encoded></item><item><title>Artifact 與可重播性</title><link>https://tarrragon.github.io/blog/ci/artifact-reproducibility/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/artifact-reproducibility/</guid><description>&lt;p>Artifact 可重播性的核心責任是讓每次發布都能追到同一份被驗證的產物。CI/CD 不只是在 runner 上跑命令；它要回答「測試通過的是哪份內容」「發布出去的是哪份內容」「事故時如何找回同一份內容」。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">Artifact&lt;/a> 是 CI/CD 流程中的交付單位。前端可能是 &lt;code>dist/&lt;/code>，後端可能是 binary 或 image，App 可能是 IPA / AAB，資料任務可能是 DAG 或 query package；不同形式的 artifact 都承擔同一個責任：把 source change 轉成可驗證、可保存、可推進的產物。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>能力&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Build once&lt;/td>
 &lt;td>同一次變更只產生一次正式 artifact&lt;/td>
 &lt;td>build job 是否保存產物&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Verify once&lt;/td>
 &lt;td>測試同一份 artifact&lt;/td>
 &lt;td>test job 是否 download artifact&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff&lt;/a>&lt;/td>
 &lt;td>在 job / workflow 間交接產物&lt;/td>
 &lt;td>checksum、digest、version 是否一致&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Promote same artifact&lt;/td>
 &lt;td>staging / production 推進同一份&lt;/td>
 &lt;td>production 是否重新 build&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recover artifact&lt;/td>
 &lt;td>事故時找回上一份可用產物&lt;/td>
 &lt;td>retention、release、registry 是否保留&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Build once 的責任是降低環境漂移。若 test job 與 deploy job 各自 build，一個 lockfile、環境變數或 base image 差異就能讓兩份產物不同；此時 CI 綠燈不再能證明 production 內容可信。&lt;/p>
&lt;p>Verify once 的責任是把測試結果綁到具體產物。測試應輸出 artifact identity，例如 checksum、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/image-digest/" data-link-title="Image Digest" data-link-desc="說明 container image digest 如何作為不可變產物身分，支撐掃描、推進與 runtime 追溯">Image Digest&lt;/a>、release asset name 或 bundle version，讓 reviewer 能確認紅綠燈對應哪份內容。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff&lt;/a> 的責任是在 job 邊界保留身分。Upload / download artifact、registry digest、release asset、package registry 與 object storage 都可以做 handoff；重點是交接時沿用既有產物。&lt;/p>
&lt;p>Promote same artifact 的責任是讓環境差異集中在設定與流量。Staging 驗證過的 image、package 或 static artifact 應被推進到 production；若 production 重新 build，就需要重新驗證 production 那份產物。&lt;/p>
&lt;p>Recover artifact 的責任是讓 rollback 有實體目標。沒有保留 artifact 的 rollback 會變成「從舊 commit 重新 build」，這會受到依賴、base image、registry、toolchain 與時間漂移影響。&lt;/p>
&lt;h2 id="可重播性檢查">可重播性檢查&lt;/h2>
&lt;p>可重播性檢查的責任是確認產物身分與建置條件足夠明確。嚴格 reproducible build 很難在所有專案做到，但 CI/CD 至少要達到「同一次 workflow 的產物可以被查詢、保存、驗證與重新部署」。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檢查項&lt;/th>
 &lt;th>判讀問題&lt;/th>
 &lt;th>常見做法&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Source&lt;/td>
 &lt;td>artifact 對應哪個 commit&lt;/td>
 &lt;td>embed git SHA / release version&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dependency&lt;/td>
 &lt;td>dependency 是否固定&lt;/td>
 &lt;td>lockfile、base image digest&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Environment&lt;/td>
 &lt;td>build 環境是否固定&lt;/td>
 &lt;td>runner image、toolchain version&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Identity&lt;/td>
 &lt;td>artifact 是否有不可變身分&lt;/td>
 &lt;td>checksum、digest、signature&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Retention&lt;/td>
 &lt;td>artifact 保留多久&lt;/td>
 &lt;td>release asset、registry retention&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Provenance&lt;/td>
 &lt;td>artifact 如何被產生&lt;/td>
 &lt;td>workflow run、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/sbom/" data-link-title="SBOM" data-link-desc="說明 Software Bill of Materials 如何揭露 artifact 內含元件，支撐供應鏈掃描與例外治理">SBOM&lt;/a>、attestation&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表讓團隊知道自己目前在哪個成熟度。初期可以先做到 source、dependency、identity；高治理場景再補 SBOM、signature 與 provenance。&lt;/p></description><content:encoded><![CDATA[<p>Artifact 可重播性的核心責任是讓每次發布都能追到同一份被驗證的產物。CI/CD 不只是在 runner 上跑命令；它要回答「測試通過的是哪份內容」「發布出去的是哪份內容」「事故時如何找回同一份內容」。</p>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">Artifact</a> 是 CI/CD 流程中的交付單位。前端可能是 <code>dist/</code>，後端可能是 binary 或 image，App 可能是 IPA / AAB，資料任務可能是 DAG 或 query package；不同形式的 artifact 都承擔同一個責任：把 source change 轉成可驗證、可保存、可推進的產物。</p>
<table>
  <thead>
      <tr>
          <th>能力</th>
          <th>責任</th>
          <th>判讀訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build once</td>
          <td>同一次變更只產生一次正式 artifact</td>
          <td>build job 是否保存產物</td>
      </tr>
      <tr>
          <td>Verify once</td>
          <td>測試同一份 artifact</td>
          <td>test job 是否 download artifact</td>
      </tr>
      <tr>
          <td><a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff</a></td>
          <td>在 job / workflow 間交接產物</td>
          <td>checksum、digest、version 是否一致</td>
      </tr>
      <tr>
          <td>Promote same artifact</td>
          <td>staging / production 推進同一份</td>
          <td>production 是否重新 build</td>
      </tr>
      <tr>
          <td>Recover artifact</td>
          <td>事故時找回上一份可用產物</td>
          <td>retention、release、registry 是否保留</td>
      </tr>
  </tbody>
</table>
<p>Build once 的責任是降低環境漂移。若 test job 與 deploy job 各自 build，一個 lockfile、環境變數或 base image 差異就能讓兩份產物不同；此時 CI 綠燈不再能證明 production 內容可信。</p>
<p>Verify once 的責任是把測試結果綁到具體產物。測試應輸出 artifact identity，例如 checksum、<a href="/blog/ci/knowledge-cards/image-digest/" data-link-title="Image Digest" data-link-desc="說明 container image digest 如何作為不可變產物身分，支撐掃描、推進與 runtime 追溯">Image Digest</a>、release asset name 或 bundle version，讓 reviewer 能確認紅綠燈對應哪份內容。</p>
<p><a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact handoff</a> 的責任是在 job 邊界保留身分。Upload / download artifact、registry digest、release asset、package registry 與 object storage 都可以做 handoff；重點是交接時沿用既有產物。</p>
<p>Promote same artifact 的責任是讓環境差異集中在設定與流量。Staging 驗證過的 image、package 或 static artifact 應被推進到 production；若 production 重新 build，就需要重新驗證 production 那份產物。</p>
<p>Recover artifact 的責任是讓 rollback 有實體目標。沒有保留 artifact 的 rollback 會變成「從舊 commit 重新 build」，這會受到依賴、base image、registry、toolchain 與時間漂移影響。</p>
<h2 id="可重播性檢查">可重播性檢查</h2>
<p>可重播性檢查的責任是確認產物身分與建置條件足夠明確。嚴格 reproducible build 很難在所有專案做到，但 CI/CD 至少要達到「同一次 workflow 的產物可以被查詢、保存、驗證與重新部署」。</p>
<table>
  <thead>
      <tr>
          <th>檢查項</th>
          <th>判讀問題</th>
          <th>常見做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>artifact 對應哪個 commit</td>
          <td>embed git SHA / release version</td>
      </tr>
      <tr>
          <td>Dependency</td>
          <td>dependency 是否固定</td>
          <td>lockfile、base image digest</td>
      </tr>
      <tr>
          <td>Environment</td>
          <td>build 環境是否固定</td>
          <td>runner image、toolchain version</td>
      </tr>
      <tr>
          <td>Identity</td>
          <td>artifact 是否有不可變身分</td>
          <td>checksum、digest、signature</td>
      </tr>
      <tr>
          <td>Retention</td>
          <td>artifact 保留多久</td>
          <td>release asset、registry retention</td>
      </tr>
      <tr>
          <td>Provenance</td>
          <td>artifact 如何被產生</td>
          <td>workflow run、<a href="/blog/ci/knowledge-cards/sbom/" data-link-title="SBOM" data-link-desc="說明 Software Bill of Materials 如何揭露 artifact 內含元件，支撐供應鏈掃描與例外治理">SBOM</a>、attestation</td>
      </tr>
  </tbody>
</table>
<p>這張表讓團隊知道自己目前在哪個成熟度。初期可以先做到 source、dependency、identity；高治理場景再補 SBOM、signature 與 provenance。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>反模式的共同問題是讓「綠燈」失去指向性。當綠燈不知道對應哪份產物，CI/CD 只剩下命令執行紀錄。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險</th>
          <th>替代做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>test 與 deploy 各自 build</td>
          <td>測試與發布內容漂移</td>
          <td>build once，artifact handoff</td>
      </tr>
      <tr>
          <td>rollback 重新 build 舊 commit</td>
          <td>舊 commit 可能產出不同內容</td>
          <td>保留上一份 release artifact</td>
      </tr>
      <tr>
          <td>只用人類可讀 tag</td>
          <td>tag 可被覆寫或語意不精準</td>
          <td>搭配 checksum / digest</td>
      </tr>
      <tr>
          <td>artifact retention 太短</td>
          <td>事故時找不到可回復版本</td>
          <td>對 release artifact 設長期保留</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Artifact 術語：讀 <a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">Artifact</a>。</li>
<li>Artifact handoff：讀 <a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">Artifact Handoff</a>。</li>
<li>Gate 邊界：讀 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
</ul>
]]></content:encoded></item><item><title>Flaky test 治理</title><link>https://tarrragon.github.io/blog/ci/flaky-test-governance/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/flaky-test-governance/</guid><description>&lt;p>Flaky test 治理的核心責任是保護 CI gate 的信任度。&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky test&lt;/a> 會讓團隊開始用重跑取代判讀，最後讓紅燈失去阻擋意義。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Flaky test 是非決定性的 gate 訊號。它的危害不只在延遲 merge，而是在心理上訓練團隊忽略紅燈；當真回歸出現時，大家也可能先按 rerun。治理目標是把 flaky 分類、隔離、修復，並保持 required checks 的語意可信。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Detect&lt;/td>
 &lt;td>找出非決定性失敗&lt;/td>
 &lt;td>同 commit 重跑結果不一致&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Classify&lt;/td>
 &lt;td>區分測試、環境、資料與產品問題&lt;/td>
 &lt;td>failure pattern、log、trace&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Contain&lt;/td>
 &lt;td>降低對主線 gate 的污染&lt;/td>
 &lt;td>quarantine、owner、expiry&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fix&lt;/td>
 &lt;td>修掉根因&lt;/td>
 &lt;td>timing、isolation、mock、resource&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Re-admit&lt;/td>
 &lt;td>恢復 gate 信任&lt;/td>
 &lt;td>連續穩定、觀測窗口、owner sign-off&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Detect 階段負責證明 flakiness。單次失敗不應直接貼 flaky 標籤；要看同一 commit、同一測試、相近環境下是否出現 pass / fail 不一致，並保存 log、trace、screenshot 或 seed。&lt;/p>
&lt;p>Classify 階段負責找根因方向。常見來源包含時間競態、測試順序依賴、共享狀態、外部服務、隨機資料、資源不足、瀏覽器 layout timing、網路模擬與 CI runner 差異；不同來源需要不同修法。&lt;/p>
&lt;p>Contain 階段負責保護主線。高價值但暫時 flaky 的測試可以進 quarantine workflow，但必須有 owner、issue、到期日與 replacement gate；直接從 required checks 移除而不追蹤，等於降低品質基線。&lt;/p>
&lt;p>Fix 階段負責消除非決定性。常見修法是移除固定 sleep、改用可觀察條件等待、隔離資料、固定 random seed、避免測試共享全域狀態、mock 不穩定外部依賴或調整資源限制。&lt;/p>
&lt;p>Re-admit 階段負責把測試放回 gate。測試修完後應在多次 workflow、不同 runner 或足夠時間窗口中穩定通過，再恢復 required checks；否則 gate 會反覆被污染。&lt;/p>
&lt;h2 id="分類矩陣">分類矩陣&lt;/h2>
&lt;p>分類矩陣的責任是讓 flaky issue 有明確修復路由。沒有分類時，團隊容易只留下「偶發失敗」這種不可執行標籤。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>常見訊號&lt;/th>
 &lt;th>修復方向&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Timing&lt;/td>
 &lt;td>sleep 不足、元素尚未出現&lt;/td>
 &lt;td>等待可觀察條件、移除固定 sleep&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shared state&lt;/td>
 &lt;td>單跑通過、整批失敗&lt;/td>
 &lt;td>隔離資料、清理全域狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Order&lt;/td>
 &lt;td>測試順序改變後失敗&lt;/td>
 &lt;td>移除順序依賴、獨立 setup&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>External&lt;/td>
 &lt;td>第三方 API、網路或時間服務不穩&lt;/td>
 &lt;td>mock、contract fixture、retry boundary&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resource&lt;/td>
 &lt;td>CI runner 負載高時失敗&lt;/td>
 &lt;td>降低 parallelism、設定 resource&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Product race&lt;/td>
 &lt;td>真實功能存在競態&lt;/td>
 &lt;td>回到產品修復，不只改測試&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表的邊界是：flaky 可能來自測試，也可能來自產品 race condition。若測試揭露的是產品 race condition，它應該被當成真 bug 處理。&lt;/p>
&lt;h2 id="quarantine-契約">Quarantine 契約&lt;/h2>
&lt;p>Quarantine 的責任是暫時隔離污染，並維持 gate 的長期品質基線。隔離測試時，要把責任、期限與替代風險控制寫清楚。&lt;/p>
&lt;ol>
&lt;li>每個 quarantine test 必須有 issue 與 owner。&lt;/li>
&lt;li>每個 issue 必須標明分類、失敗證據與修復方向。&lt;/li>
&lt;li>Required checks 若移除測試，要補 replacement gate 或風險說明。&lt;/li>
&lt;li>Quarantine workflow 仍需定期跑，並回報趨勢。&lt;/li>
&lt;li>到期未修復時要重新評估：修、刪、改寫或降級測試責任。&lt;/li>
&lt;/ol>
&lt;p>這個契約讓 quarantine 成為治理工具。沒有期限與 owner 的 quarantine 會變成測試墓地，讓主線 gate 永久失去一部分覆蓋。&lt;/p>
&lt;h2 id="tripwire">Tripwire&lt;/h2>
&lt;p>Tripwire 的責任是提示 flaky 已經從局部問題變成流程問題。&lt;/p>
&lt;ul>
&lt;li>團隊看到紅燈第一反應是 rerun：暫停重跑習慣，要求先分類失敗。&lt;/li>
&lt;li>同一測試一週內多次 quarantine：提升到測試架構或產品 race 檢討。&lt;/li>
&lt;li>Required checks 常因環境問題失敗：檢查 runner、resource、cache 與外部依賴。&lt;/li>
&lt;li>Flaky issue 沒 owner 或沒期限：把 quarantine 視為未完成修復，不視為已處理。&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Flaky 術語：讀 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky Test&lt;/a>。&lt;/li>
&lt;li>Failure routing：讀 &lt;a href="../github-actions-failure-flow/">CI 失敗到修復發布流程&lt;/a>。&lt;/li>
&lt;li>Gate 邊界：讀 &lt;a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界&lt;/a>。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Flaky test 治理的核心責任是保護 CI gate 的信任度。<a href="/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky test</a> 會讓團隊開始用重跑取代判讀，最後讓紅燈失去阻擋意義。</p>
<h2 id="概念定位">概念定位</h2>
<p>Flaky test 是非決定性的 gate 訊號。它的危害不只在延遲 merge，而是在心理上訓練團隊忽略紅燈；當真回歸出現時，大家也可能先按 rerun。治理目標是把 flaky 分類、隔離、修復，並保持 required checks 的語意可信。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>責任</th>
          <th>判讀訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Detect</td>
          <td>找出非決定性失敗</td>
          <td>同 commit 重跑結果不一致</td>
      </tr>
      <tr>
          <td>Classify</td>
          <td>區分測試、環境、資料與產品問題</td>
          <td>failure pattern、log、trace</td>
      </tr>
      <tr>
          <td>Contain</td>
          <td>降低對主線 gate 的污染</td>
          <td>quarantine、owner、expiry</td>
      </tr>
      <tr>
          <td>Fix</td>
          <td>修掉根因</td>
          <td>timing、isolation、mock、resource</td>
      </tr>
      <tr>
          <td>Re-admit</td>
          <td>恢復 gate 信任</td>
          <td>連續穩定、觀測窗口、owner sign-off</td>
      </tr>
  </tbody>
</table>
<p>Detect 階段負責證明 flakiness。單次失敗不應直接貼 flaky 標籤；要看同一 commit、同一測試、相近環境下是否出現 pass / fail 不一致，並保存 log、trace、screenshot 或 seed。</p>
<p>Classify 階段負責找根因方向。常見來源包含時間競態、測試順序依賴、共享狀態、外部服務、隨機資料、資源不足、瀏覽器 layout timing、網路模擬與 CI runner 差異；不同來源需要不同修法。</p>
<p>Contain 階段負責保護主線。高價值但暫時 flaky 的測試可以進 quarantine workflow，但必須有 owner、issue、到期日與 replacement gate；直接從 required checks 移除而不追蹤，等於降低品質基線。</p>
<p>Fix 階段負責消除非決定性。常見修法是移除固定 sleep、改用可觀察條件等待、隔離資料、固定 random seed、避免測試共享全域狀態、mock 不穩定外部依賴或調整資源限制。</p>
<p>Re-admit 階段負責把測試放回 gate。測試修完後應在多次 workflow、不同 runner 或足夠時間窗口中穩定通過，再恢復 required checks；否則 gate 會反覆被污染。</p>
<h2 id="分類矩陣">分類矩陣</h2>
<p>分類矩陣的責任是讓 flaky issue 有明確修復路由。沒有分類時，團隊容易只留下「偶發失敗」這種不可執行標籤。</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>常見訊號</th>
          <th>修復方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Timing</td>
          <td>sleep 不足、元素尚未出現</td>
          <td>等待可觀察條件、移除固定 sleep</td>
      </tr>
      <tr>
          <td>Shared state</td>
          <td>單跑通過、整批失敗</td>
          <td>隔離資料、清理全域狀態</td>
      </tr>
      <tr>
          <td>Order</td>
          <td>測試順序改變後失敗</td>
          <td>移除順序依賴、獨立 setup</td>
      </tr>
      <tr>
          <td>External</td>
          <td>第三方 API、網路或時間服務不穩</td>
          <td>mock、contract fixture、retry boundary</td>
      </tr>
      <tr>
          <td>Resource</td>
          <td>CI runner 負載高時失敗</td>
          <td>降低 parallelism、設定 resource</td>
      </tr>
      <tr>
          <td>Product race</td>
          <td>真實功能存在競態</td>
          <td>回到產品修復，不只改測試</td>
      </tr>
  </tbody>
</table>
<p>這張表的邊界是：flaky 可能來自測試，也可能來自產品 race condition。若測試揭露的是產品 race condition，它應該被當成真 bug 處理。</p>
<h2 id="quarantine-契約">Quarantine 契約</h2>
<p>Quarantine 的責任是暫時隔離污染，並維持 gate 的長期品質基線。隔離測試時，要把責任、期限與替代風險控制寫清楚。</p>
<ol>
<li>每個 quarantine test 必須有 issue 與 owner。</li>
<li>每個 issue 必須標明分類、失敗證據與修復方向。</li>
<li>Required checks 若移除測試，要補 replacement gate 或風險說明。</li>
<li>Quarantine workflow 仍需定期跑，並回報趨勢。</li>
<li>到期未修復時要重新評估：修、刪、改寫或降級測試責任。</li>
</ol>
<p>這個契約讓 quarantine 成為治理工具。沒有期限與 owner 的 quarantine 會變成測試墓地，讓主線 gate 永久失去一部分覆蓋。</p>
<h2 id="tripwire">Tripwire</h2>
<p>Tripwire 的責任是提示 flaky 已經從局部問題變成流程問題。</p>
<ul>
<li>團隊看到紅燈第一反應是 rerun：暫停重跑習慣，要求先分類失敗。</li>
<li>同一測試一週內多次 quarantine：提升到測試架構或產品 race 檢討。</li>
<li>Required checks 常因環境問題失敗：檢查 runner、resource、cache 與外部依賴。</li>
<li>Flaky issue 沒 owner 或沒期限：把 quarantine 視為未完成修復，不視為已處理。</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Flaky 術語：讀 <a href="/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky Test</a>。</li>
<li>Failure routing：讀 <a href="../github-actions-failure-flow/">CI 失敗到修復發布流程</a>。</li>
<li>Gate 邊界：讀 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
</ul>
]]></content:encoded></item></channel></rss>