<?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>Context on Tarragon</title><link>https://tarrragon.github.io/blog/tags/context/</link><description>Recent content in Context on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 14 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/context/index.xml" rel="self" type="application/rss+xml"/><item><title>Context Packing</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/context-packing/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/context-packing/</guid><description>&lt;p>Context packing 的核心概念是「&lt;strong>retrieve 拿到候選 chunks 後，決定哪些內容、以什麼順序、帶哪些 metadata 塞進 prompt&lt;/strong>」。它是 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a> 在 retrieval 與 generation 之間的 context 組裝層，有別於 retrieval 本身。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Context packing 位在 top-k retrieval 結果與 LLM prompt 之間。它跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">retrieval source&lt;/a> 相鄰，因為來源 metadata 會影響引用；也跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle&lt;/a> 相鄰，因為 chunk 順序會影響模型注意力。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>常見 packing 決策包含 dedup 重複 chunk、把最相關內容放前後、按 document order 保留段落流、摘要或壓縮過長 chunks、在每段前加 source path 與 score。這些決策會改變答案品質、token cost 與可追溯性。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>設計 context packing 時要回答：哪些 chunk 真的要進 prompt、順序如何安排、是否保留來源、是否需要 summarization / compression。高追溯場景優先保留 source metadata；長 context 場景要避免把重要 chunk 放在中間；latency 敏感場景要限制 top-k 與 compression call。&lt;/p></description><content:encoded><![CDATA[<p>Context packing 的核心概念是「<strong>retrieve 拿到候選 chunks 後，決定哪些內容、以什麼順序、帶哪些 metadata 塞進 prompt</strong>」。它是 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> 在 retrieval 與 generation 之間的 context 組裝層，有別於 retrieval 本身。</p>
<h2 id="概念位置">概念位置</h2>
<p>Context packing 位在 top-k retrieval 結果與 LLM prompt 之間。它跟 <a href="/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">retrieval source</a> 相鄰，因為來源 metadata 會影響引用；也跟 <a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle</a> 相鄰，因為 chunk 順序會影響模型注意力。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>常見 packing 決策包含 dedup 重複 chunk、把最相關內容放前後、按 document order 保留段落流、摘要或壓縮過長 chunks、在每段前加 source path 與 score。這些決策會改變答案品質、token cost 與可追溯性。</p>
<h2 id="設計責任">設計責任</h2>
<p>設計 context packing 時要回答：哪些 chunk 真的要進 prompt、順序如何安排、是否保留來源、是否需要 summarization / compression。高追溯場景優先保留 source metadata；長 context 場景要避免把重要 chunk 放在中間；latency 敏感場景要限制 top-k 與 compression call。</p>
]]></content:encoded></item><item><title>6.1 graceful shutdown 與 signal handling</title><link>https://tarrragon.github.io/blog/go-advanced/06-production-operations/graceful-shutdown/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go-advanced/06-production-operations/graceful-shutdown/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">Graceful shutdown&lt;/a> 的核心目標是服務收到停止訊號後，不再接受新工作，並給既有工作一段時間完成或清理。Go 服務通常用 signal、root context、&lt;code>http.Server.Shutdown&lt;/code>、worker context 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 串起停止流程。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>把 OS signal 轉成 root context 取消&lt;/li>
&lt;li>用 &lt;code>http.Server.Shutdown&lt;/code> 停止接受新 request&lt;/li>
&lt;li>讓 worker、hub、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> pump 觀察同一個停止訊號&lt;/li>
&lt;li>設計 shutdown timeout 與強制退出邊界&lt;/li>
&lt;li>測試 server 與 worker 的停止流程&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察直接結束-process-會留下不確定狀態">【觀察】直接結束 process 會留下不確定狀態&lt;/h2>
&lt;p>Shutdown 的核心風險是停止流程不明確。服務可能正在處理 request、WebSocket client 仍在線、worker 正在寫資料、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> message 尚未 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ack-nack/" data-link-title="Ack / Nack" data-link-desc="說明 consumer 如何向 broker 回報訊息處理結果">ack&lt;/a>、diagnostics 還以為服務可接流量。&lt;/p>
&lt;p>不完整停止常見後果：&lt;/p>
&lt;ul>
&lt;li>新 request 在服務即將關閉時仍被接受。&lt;/li>
&lt;li>WebSocket client 沒收到 close，server 端 goroutine 殘留。&lt;/li>
&lt;li>背景 worker 寫到一半被中斷。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> 還是 200，負載平衡器繼續送流量。&lt;/li>
&lt;li>測試結束後留下 goroutine 或開放 port。&lt;/li>
&lt;/ul>
&lt;p>Graceful shutdown 是讓停止策略可預期。&lt;/p>
&lt;h2 id="判讀shutdown-是多階段流程">【判讀】shutdown 是多階段流程&lt;/h2>
&lt;p>Graceful shutdown 的核心流程是先停止接新工作，再讓既有工作收尾，最後釋放資源。&lt;/p>
&lt;p>建議順序：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">receive SIGINT/SIGTERM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">cancel root context
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> ├── readiness becomes false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ├── HTTP server stops accepting new requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> ├── workers stop consuming new jobs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> ├── WebSocket hub unregisters clients
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> └── diagnostics/log records shutdown reason
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">wait within timeout
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">process exits&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不同服務會有不同細節，但核心不變：停止訊號要集中，元件各自完成自己的 cleanup，整體流程要有 timeout。&lt;/p>
&lt;h2 id="執行signal-轉成-root-context">【執行】signal 轉成 root context&lt;/h2>
&lt;p>Signal handling 的核心責任是把作業系統訊號轉成應用程式可理解的取消訊號。Go 1.16 之後可以使用 &lt;code>signal.NotifyContext&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">stop&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">signal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NotifyContext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Interrupt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>ctx&lt;/code> 是 root context。HTTP server、worker、hub、diagnostics 都應從它派生出自己的 lifecycle，而不是每個元件各自監聽 signal。&lt;/p>
&lt;p>Signal handler 不應放大量清理邏輯。它只負責發出停止意圖；實際清理由各元件在自己的 ownership 邊界內完成。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">Graceful shutdown</a> 的核心目標是服務收到停止訊號後，不再接受新工作，並給既有工作一段時間完成或清理。Go 服務通常用 signal、root context、<code>http.Server.Shutdown</code>、worker context 與 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 串起停止流程。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>把 OS signal 轉成 root context 取消</li>
<li>用 <code>http.Server.Shutdown</code> 停止接受新 request</li>
<li>讓 worker、hub、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> pump 觀察同一個停止訊號</li>
<li>設計 shutdown timeout 與強制退出邊界</li>
<li>測試 server 與 worker 的停止流程</li>
</ol>
<hr>
<h2 id="觀察直接結束-process-會留下不確定狀態">【觀察】直接結束 process 會留下不確定狀態</h2>
<p>Shutdown 的核心風險是停止流程不明確。服務可能正在處理 request、WebSocket client 仍在線、worker 正在寫資料、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> message 尚未 <a href="/blog/backend/knowledge-cards/ack-nack/" data-link-title="Ack / Nack" data-link-desc="說明 consumer 如何向 broker 回報訊息處理結果">ack</a>、diagnostics 還以為服務可接流量。</p>
<p>不完整停止常見後果：</p>
<ul>
<li>新 request 在服務即將關閉時仍被接受。</li>
<li>WebSocket client 沒收到 close，server 端 goroutine 殘留。</li>
<li>背景 worker 寫到一半被中斷。</li>
<li><a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> 還是 200，負載平衡器繼續送流量。</li>
<li>測試結束後留下 goroutine 或開放 port。</li>
</ul>
<p>Graceful shutdown 是讓停止策略可預期。</p>
<h2 id="判讀shutdown-是多階段流程">【判讀】shutdown 是多階段流程</h2>
<p>Graceful shutdown 的核心流程是先停止接新工作，再讓既有工作收尾，最後釋放資源。</p>
<p>建議順序：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">receive SIGINT/SIGTERM
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">        │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        ▼
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">cancel root context
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        ├── readiness becomes false
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        ├── HTTP server stops accepting new requests
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        ├── workers stop consuming new jobs
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        ├── WebSocket hub unregisters clients
</span></span><span class="line"><span class="ln">10</span><span class="cl">        └── diagnostics/log records shutdown reason
</span></span><span class="line"><span class="ln">11</span><span class="cl">        │
</span></span><span class="line"><span class="ln">12</span><span class="cl">        ▼
</span></span><span class="line"><span class="ln">13</span><span class="cl">wait within timeout
</span></span><span class="line"><span class="ln">14</span><span class="cl">        │
</span></span><span class="line"><span class="ln">15</span><span class="cl">        ▼
</span></span><span class="line"><span class="ln">16</span><span class="cl">process exits</span></span></code></pre></div><p>不同服務會有不同細節，但核心不變：停止訊號要集中，元件各自完成自己的 cleanup，整體流程要有 timeout。</p>
<h2 id="執行signal-轉成-root-context">【執行】signal 轉成 root context</h2>
<p>Signal handling 的核心責任是把作業系統訊號轉成應用程式可理解的取消訊號。Go 1.16 之後可以使用 <code>signal.NotifyContext</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">ctx</span><span class="p">,</span> <span class="nx">stop</span> <span class="o">:=</span> <span class="nx">signal</span><span class="p">.</span><span class="nf">NotifyContext</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Interrupt</span><span class="p">,</span> <span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGTERM</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">defer</span> <span class="nf">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">run</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>ctx</code> 是 root context。HTTP server、worker、hub、diagnostics 都應從它派生出自己的 lifecycle，而不是每個元件各自監聽 signal。</p>
<p>Signal handler 不應放大量清理邏輯。它只負責發出停止意圖；實際清理由各元件在自己的 ownership 邊界內完成。</p>
<h2 id="執行http-server-用-shutdown-停止接新-request">【執行】HTTP server 用 Shutdown 停止接新 request</h2>
<p><code>http.Server.Shutdown</code> 的核心行為是停止接受新連線，並等待既有 request 在 timeout 內完成。它比直接 <code>Close</code> 更適合 graceful shutdown。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">RunHTTPServer</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">handler</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">server</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="nx">Addr</span><span class="p">:</span>    <span class="s">&#34;:8080&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nx">Handler</span><span class="p">:</span> <span class="nx">handler</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">errCh</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kt">error</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">errCh</span> <span class="o">&lt;-</span> <span class="nx">server</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">}()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="nx">shutdownCtx</span><span class="p">,</span> <span class="nx">cancel</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">defer</span> <span class="nf">cancel</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="nx">server</span><span class="p">.</span><span class="nf">Shutdown</span><span class="p">(</span><span class="nx">shutdownCtx</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">case</span> <span class="nx">err</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">errCh</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">if</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ErrServerClosed</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Shutdown timeout 是必要邊界。沒有 timeout 的 shutdown 可能永遠等待某個卡住 request；timeout 太短則可能讓合理 request 來不及收尾。</p>
<h2 id="策略readiness-應先變成-false">【策略】readiness 應先變成 false</h2>
<p>Readiness 的核心用途是控制服務是否應接新流量。Shutdown 開始後，readiness 應先變成 false，再停止 server 或等待既有工作。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">Lifecycle</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">shuttingDown</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">Bool</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">l</span> <span class="o">*</span><span class="nx">Lifecycle</span><span class="p">)</span> <span class="nf">BeginShutdown</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">l</span><span class="p">.</span><span class="nx">shuttingDown</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">l</span> <span class="o">*</span><span class="nx">Lifecycle</span><span class="p">)</span> <span class="nf">Ready</span><span class="p">()</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="p">!</span><span class="nx">l</span><span class="p">.</span><span class="nx">shuttingDown</span><span class="p">.</span><span class="nf">Load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Signal 收到後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">lifecycle</span><span class="p">.</span><span class="nf">BeginShutdown</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nf">cancel</span><span class="p">()</span></span></span></code></pre></div><p>這讓負載平衡器或監控能知道服務不應再接新流量。Process 還活著，但 readiness 已經反映操作狀態。</p>
<h2 id="執行背景工作要觀察-context">【執行】背景工作要觀察 context</h2>
<p>背景 worker 的核心 shutdown 條件是每個 loop 都能觀察停止訊號。Ticker、queue <a href="/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer</a>、WebSocket hub 都應該有退出路徑。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">RunWorker</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">ticker</span> <span class="o">:=</span> <span class="nx">time</span><span class="p">.</span><span class="nf">NewTicker</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Minute</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">defer</span> <span class="nx">ticker</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">return</span> <span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">ticker</span><span class="p">.</span><span class="nx">C</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">RunOnce</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">                <span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>若 <code>RunOnce</code> 可能執行很久，也應接收 context。否則外層 loop 看到 cancel，內層 I/O 或計算仍可能卡住。</p>
<h2 id="策略websocket-cleanup-要回到-hub-owner">【策略】WebSocket cleanup 要回到 hub owner</h2>
<p>WebSocket shutdown 的核心原則是讓 hub 或 connection manager 統一清理 client。不要讓 signal handler 直接遍歷各種 connection 並隨意 close。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">h</span> <span class="o">*</span><span class="nx">Hub</span><span class="p">)</span> <span class="nf">Run</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            <span class="nx">h</span><span class="p">.</span><span class="nf">closeAllClients</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">case</span> <span class="nx">client</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">h</span><span class="p">.</span><span class="nx">register</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="nx">h</span><span class="p">.</span><span class="nf">registerClient</span><span class="p">(</span><span class="nx">client</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">case</span> <span class="nx">client</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">h</span><span class="p">.</span><span class="nx">unregister</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="nx">h</span><span class="p">.</span><span class="nf">unregisterClient</span><span class="p">(</span><span class="nx">client</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>closeAllClients</code> 應透過 hub 的既有 owner 邏輯關閉 <code>send</code>、移除訂閱、關閉 connection。這延續前面模組的 ownership 原則。</p>
<h2 id="測試shutdown-測試要觀察明確條件">【測試】shutdown 測試要觀察明確條件</h2>
<p>Shutdown 測試的核心是確認停止訊號能讓元件退出，而不是等待固定時間。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">TestWorkerStopsOnContextCancel</span><span class="p">(</span><span class="nx">t</span> <span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">ctx</span><span class="p">,</span> <span class="nx">cancel</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">done</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kd">struct</span><span class="p">{})</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">defer</span> <span class="nb">close</span><span class="p">(</span><span class="nx">done</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nx">_</span> <span class="p">=</span> <span class="nf">RunWorker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">}()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nf">cancel</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">case</span> <span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nx">t</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;worker did not stop&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>HTTP server 測試可以啟動 server 後 cancel context，確認 <code>RunHTTPServer</code> 回傳。測試應使用隨機 port 或 <code>httptest.Server</code>，避免固定 port 造成衝突。</p>
<h2 id="本章不處理">本章不處理</h2>
<p>本章先處理服務內部的 shutdown 順序與 cleanup owner；平台 hook、timeout 與 load balancer 合約，會在下列章節再往外延伸：</p>
<ul>
<li><a href="/blog/go-advanced/07-distributed-operations/deployment-contracts/" data-link-title="7.5 Kubernetes、systemd 與 load balancer 合約" data-link-desc="理解部署平台如何影響 Go 服務的 shutdown、health 與資源限制">Go 進階：Kubernetes、systemd 與 load balancer 合約</a></li>
</ul>
<h2 id="和-go-教材的關係">和 Go 教材的關係</h2>
<p>這一章承接的是 goroutine lifecycle、ticker cleanup 與 platform handoff；如果你要先回看語言教材，可以讀：</p>
<ul>
<li><a href="/blog/go/04-concurrency/goroutine/" data-link-title="4.1 goroutine：輕量並發工作" data-link-desc="用 goroutine 啟動並發工作，並設計清楚的退出條件">Go：goroutine：輕量並發工作</a></li>
<li><a href="/blog/go/03-stdlib/defer-cleanup/" data-link-title="3.8 defer 與資源清理" data-link-desc="用 defer 管理 close、unlock、cleanup 與 panic 邊界">Go：defer 與資源清理</a></li>
<li><a href="/blog/go/04-concurrency/select/" data-link-title="4.3 select：同時等待多種事件" data-link-desc="用 select 建立事件迴圈">Go：select：同時等待多種事件</a></li>
<li><a href="/blog/go-advanced/03-runtime-profiling/goroutine-leak/" data-link-title="3.3 goroutine leak 偵測" data-link-desc="判斷背景工作與 client pump 是否正確退出">Go：goroutine leak 偵測</a></li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">Backend：部署平台與網路入口</a></li>
</ul>
<h2 id="小結">小結</h2>
<p>Graceful shutdown 是多階段流程：signal 轉成 root context，readiness 先關閉，HTTP server 停止接新 request，worker 和 WebSocket hub 觀察 context 收尾，整體流程受 timeout 保護。停止訊號越集中，元件 ownership 越清楚，服務在部署、測試與本機開發時越不容易留下殘存 goroutine 或未釋放連線。</p>
]]></content:encoded></item><item><title>Mitigation 的 context-dependence：deployment 條件改變有效性</title><link>https://tarrragon.github.io/blog/report/mitigation-context-dependence/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/mitigation-context-dependence/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>資安 mitigation 的有效性不是 mitigation 本身決定的、是 mitigation × deployment 條件決定的。&lt;/strong> 同一個 mitigation 在不同 deployment / config / scale / runtime 條件下、強度光譜從「完整擋」到「等同沒部署」都可能。寫作時忽略 deployment 變數、讀者實作時用最直覺條件詮釋、實際部署條件不對 mitigation silent 失效。&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>「使用 X 保護 Y」（universal-flavored）&lt;/td>
 &lt;td>在「正常」條件下 X 防 Y&lt;/td>
 &lt;td>條件不對、X silent 失效、無人警覺&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「使用 X 保護 Y、條件 Z」&lt;/td>
 &lt;td>條件 Z 成立才用 X、否則補 W&lt;/td>
 &lt;td>條件不對時 reader 知道補 W&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>差別在於：reader 在實作 review 階段有沒有 context 變數可檢查。&lt;/p>
&lt;hr>
&lt;h2 id="情境">情境&lt;/h2>
&lt;p>資安 mitigation 在文獻 / 標準 / 教學裡常被描述成「方法 → 防什麼 threat」對應、跳過 deployment 條件這個變數。讀者讀完套到自己 deployment 上、條件可能不一致。常見的 context dimension 有四類：&lt;/p>
&lt;h3 id="context-維度-1config-完整性">Context 維度 1：Config 完整性&lt;/h3>
&lt;p>Mitigation 通常需要多個 config 同時成立才有效、單一 config 不夠：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">HTTPS 防中間人：成立條件 = TLS + HSTS + cert pinning（針對重要 endpoint）+ CT log monitoring
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 失效條件 = 只有 TLS、沒 HSTS → 第一次連線可被 downgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> 沒 cert pinning → 受信任 CA 簽出假 cert 可繞過
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">JWT 驗身分： 成立條件 = 簽章驗證 + 短 TTL + rotation + 安全儲存（HttpOnly cookie 或 secure storage）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 失效條件 = 簽章對但 TTL 太長 → token 被竊後長期可用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> XSS 可讀取 → 簽章保護被繞過
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> 沒 rotation → 一次外洩永久暴露&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>寫「使用 HTTPS」「使用 JWT」是把 mitigation 縮成單一 control name、reader 預設 default config、實際要 5-7 個 config 同時對才完整。&lt;/p>
&lt;h3 id="context-維度-2scale--多實例">Context 維度 2：Scale / 多實例&lt;/h3>
&lt;p>某些 mitigation 在單機 OK、多實例失效：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Rate limit： 單實例 = local counter、per-IP rate 控管準確
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 多實例 = 每實例各自 count、攻擊者打不同實例可繞過 N 倍上限
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> 修法 = 用 distributed counter（Redis / 共享 store）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Session 失效：單實例 = local session store、invalidate 即時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 多實例 = invalidate 訊號需 broadcast、舊 token 在其他實例還可用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> 修法 = 用 stateless token + revocation list 或 共享 session store&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Reader 看到「rate limit 防 brute force」、實作時若不知道 deployment scale、單實例 OK / 多實例 silent 失效。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>資安 mitigation 的有效性不是 mitigation 本身決定的、是 mitigation × deployment 條件決定的。</strong> 同一個 mitigation 在不同 deployment / config / scale / runtime 條件下、強度光譜從「完整擋」到「等同沒部署」都可能。寫作時忽略 deployment 變數、讀者實作時用最直覺條件詮釋、實際部署條件不對 mitigation silent 失效。</p>
<table>
  <thead>
      <tr>
          <th>描述形態</th>
          <th>讀者實作判斷</th>
          <th>部署條件不對的後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「使用 X 保護 Y」（universal-flavored）</td>
          <td>在「正常」條件下 X 防 Y</td>
          <td>條件不對、X silent 失效、無人警覺</td>
      </tr>
      <tr>
          <td>「使用 X 保護 Y、條件 Z」</td>
          <td>條件 Z 成立才用 X、否則補 W</td>
          <td>條件不對時 reader 知道補 W</td>
      </tr>
  </tbody>
</table>
<p>差別在於：reader 在實作 review 階段有沒有 context 變數可檢查。</p>
<hr>
<h2 id="情境">情境</h2>
<p>資安 mitigation 在文獻 / 標準 / 教學裡常被描述成「方法 → 防什麼 threat」對應、跳過 deployment 條件這個變數。讀者讀完套到自己 deployment 上、條件可能不一致。常見的 context dimension 有四類：</p>
<h3 id="context-維度-1config-完整性">Context 維度 1：Config 完整性</h3>
<p>Mitigation 通常需要多個 config 同時成立才有效、單一 config 不夠：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">HTTPS 防中間人：成立條件 = TLS + HSTS + cert pinning（針對重要 endpoint）+ CT log monitoring
</span></span><span class="line"><span class="ln">2</span><span class="cl">                失效條件 = 只有 TLS、沒 HSTS → 第一次連線可被 downgrade
</span></span><span class="line"><span class="ln">3</span><span class="cl">                          沒 cert pinning → 受信任 CA 簽出假 cert 可繞過
</span></span><span class="line"><span class="ln">4</span><span class="cl">JWT 驗身分：    成立條件 = 簽章驗證 + 短 TTL + rotation + 安全儲存（HttpOnly cookie 或 secure storage）
</span></span><span class="line"><span class="ln">5</span><span class="cl">                失效條件 = 簽章對但 TTL 太長 → token 被竊後長期可用
</span></span><span class="line"><span class="ln">6</span><span class="cl">                          XSS 可讀取 → 簽章保護被繞過
</span></span><span class="line"><span class="ln">7</span><span class="cl">                          沒 rotation → 一次外洩永久暴露</span></span></code></pre></div><p>寫「使用 HTTPS」「使用 JWT」是把 mitigation 縮成單一 control name、reader 預設 default config、實際要 5-7 個 config 同時對才完整。</p>
<h3 id="context-維度-2scale--多實例">Context 維度 2：Scale / 多實例</h3>
<p>某些 mitigation 在單機 OK、多實例失效：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Rate limit： 單實例 = local counter、per-IP rate 控管準確
</span></span><span class="line"><span class="ln">2</span><span class="cl">            多實例 = 每實例各自 count、攻擊者打不同實例可繞過 N 倍上限
</span></span><span class="line"><span class="ln">3</span><span class="cl">            修法 = 用 distributed counter（Redis / 共享 store）
</span></span><span class="line"><span class="ln">4</span><span class="cl">Session 失效：單實例 = local session store、invalidate 即時
</span></span><span class="line"><span class="ln">5</span><span class="cl">            多實例 = invalidate 訊號需 broadcast、舊 token 在其他實例還可用
</span></span><span class="line"><span class="ln">6</span><span class="cl">            修法 = 用 stateless token + revocation list 或 共享 session store</span></span></code></pre></div><p>Reader 看到「rate limit 防 brute force」、實作時若不知道 deployment scale、單實例 OK / 多實例 silent 失效。</p>
<h3 id="context-維度-3runtime-環境">Context 維度 3：Runtime 環境</h3>
<p>執行環境差異改變 mitigation 適用性：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Cookie SameSite=Strict 防 CSRF：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  瀏覽器環境 = 有效（瀏覽器強制執行）
</span></span><span class="line"><span class="ln">3</span><span class="cl">  Native app webview = 部分有效（依 webview 實作）
</span></span><span class="line"><span class="ln">4</span><span class="cl">  Mobile in-app browser = 不一定有效（看實作）
</span></span><span class="line"><span class="ln">5</span><span class="cl">  Server-to-server = 不適用（無 cookie / 無 SameSite 概念）
</span></span><span class="line"><span class="ln">6</span><span class="cl">CSP 防 XSS：
</span></span><span class="line"><span class="ln">7</span><span class="cl">  Modern browser = 有效
</span></span><span class="line"><span class="ln">8</span><span class="cl">  舊瀏覽器（IE / 非 evergreen）= partial 或無效
</span></span><span class="line"><span class="ln">9</span><span class="cl">  非 browser execution（Electron / native webview）= 看 implementation</span></span></code></pre></div><h3 id="context-維度-4threat-actor-能力">Context 維度 4：Threat actor 能力</h3>
<p>Mitigation 的 work factor 跟 threat actor 計算能力對應：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">bcrypt（work factor = 10）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  個人攻擊者 = 強保護
</span></span><span class="line"><span class="ln">3</span><span class="cl">  Nation-state（GPU farm / FPGA）= 弱保護、需提高 work factor 或換 argon2
</span></span><span class="line"><span class="ln">4</span><span class="cl">PBKDF2（100k iterations）：
</span></span><span class="line"><span class="ln">5</span><span class="cl">  2010 年 = 強
</span></span><span class="line"><span class="ln">6</span><span class="cl">  2026 年 = 弱（建議升級到 600k+ 或 argon2）</span></span></code></pre></div><p>Threat actor 能力是 deployment 隨時間變化的變數、寫作時固定描述很快過時。</p>
<hr>
<h2 id="理想做法">理想做法</h2>
<p>每個 mitigation 段落明示三類條件：</p>
<h3 id="三類條件模板">三類條件模板</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[Mitigation X]
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 成立條件：[X 發揮設計強度需要的 config / scale / runtime / 其他 control 配套]
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 失效條件：[條件不對時 X 變成 etc 等同沒部署的具體情境]
</span></span><span class="line"><span class="ln">4</span><span class="cl">- Deployment 變數：[實作時要檢查的 dimension list]</span></span></code></pre></div><p>例（rate limit 防 brute force）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">per-IP rate limit
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 成立條件：單實例部署 OR 多實例 + distributed counter（Redis / 共享 store）
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 失效條件：多實例 + local counter、攻擊者輪流打不同實例繞過上限
</span></span><span class="line"><span class="ln">4</span><span class="cl">- Deployment 變數：實例數量、counter 部署位置（local / shared）、IP 來源真實性（NAT / proxy 後是否還能 distinguish）</span></span></code></pre></div><p>例（HTTPS 防中間人）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">HTTPS
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 成立條件：TLS + HSTS（避免首連線 downgrade）+ 受信 CA chain + 在重要 endpoint 配 cert pinning
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 失效條件：沒 HSTS → 首次連線 downgrade；CA 被攻陷 → 假 cert 可繞；no cert pinning + state-level CA 攻陷 → silent MITM
</span></span><span class="line"><span class="ln">4</span><span class="cl">- Deployment 變數：HSTS preload / max-age 設定、cert pinning 範圍（哪些 endpoint）、CA list 是否最小化、CT log monitoring 是否到位</span></span></code></pre></div><h3 id="context-描述的層次規則">Context 描述的層次規則</h3>
<p>每個 mitigation 描述至少要有 deployment baseline 跟 stretch case：</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Baseline 條件</td>
          <td>最常見 deployment（單機 / 標準 config / mainstream browser）下的有效性</td>
      </tr>
      <tr>
          <td>Stretch 條件</td>
          <td>scale / 異常 runtime / 高能力 actor 下的衰減</td>
      </tr>
      <tr>
          <td>Trigger condition</td>
          <td>何時 baseline 不夠、要升級到 stretch 的訊號</td>
      </tr>
  </tbody>
</table>
<p>baseline 給 reader 入門條件、stretch 給 reader 升級判準、trigger 讓升級成 actionable signal。</p>
<h3 id="跟規模改變可行性的同骨">跟「規模改變可行性」的同骨</h3>
<p>跟 <a href="../dataset-scale-changes-feasibility/">#89 Dataset 規模改變什麼可行</a> 同骨——#89 在 dataset / index / cache 維度、本卡在 mitigation / config / scale 維度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">#89:    &lt; 1MB 無腦處理 → 1-10MB O(N) 可行 → &gt; 100MB 強制 index
</span></span><span class="line"><span class="ln">2</span><span class="cl">本卡：   單實例 local rate limit OK → 多實例需 distributed counter → 高 scale 需 token bucket + adaptive</span></span></code></pre></div><p>「在 X 規模 / 條件下 Y 方法 OK」這個結構在資料處理跟資安都成立、是 deployment 變數驅動的工程光譜。</p>
<hr>
<h2 id="沒這樣做的麻煩">沒這樣做的麻煩</h2>
<h3 id="正常條件下有效silent-變成生產破口">「正常條件下有效」silent 變成生產破口</h3>
<p>讀者讀「使用 X 防 Y」、用自己 deployment 的 default config 實作、跑開發測試 OK、ship 進生產。生產可能是多實例 / 高 scale / 異常 runtime、X 在那條件下不成立、threat 進入。<strong>Mitigation 在開發環境 silent 失效、生產環境 silent 失效——兩階段都沒訊號、直到事件</strong>。</p>
<p>跟 <a href="../false-sense-of-security-as-primary-failure/">#100 false sense of security</a> 同病：context 沒寫、reader 用最直覺條件詮釋、condition mismatch 不會被 catch。</p>
<h3 id="mitigation-升級的時機不可-trace">Mitigation 升級的時機不可 trace</h3>
<p>威脅環境變化（actor 計算能力 / 攻擊變體 / scale 增長）需要 mitigation 跟著升級。Context 寫清楚的 mitigation 可 trace（bcrypt work factor 跟 actor 能力對應、定期 review）；context 含糊的 mitigation 不可 trace（「使用 bcrypt」變成 frozen「最佳實踐」、實際強度跟著時間 decay）。</p>
<h3 id="跨環境-deployment-的-mitigation-假設衝突">跨環境 deployment 的 mitigation 假設衝突</h3>
<p>同一份教學 / spec 套到不同 deployment（dev / staging / prod / 多區域 / 不同租戶）、若 context 沒寫、各 deployment 的 mitigation 強度差異被 silent。Audit 跨 deployment 時無法判定哪個強度最弱、整個系統的 baseline 取決於最弱 deployment、但沒人知道哪個是最弱。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../dataset-scale-changes-feasibility/">#89 Dataset 規模改變什麼可行</a></td>
          <td><strong>同骨 sibling</strong> — #89 是「資料規模 → 處理方法可行性」、本卡是「deployment 條件 → mitigation 有效性」、都是「條件變數驅動的方法光譜」</td>
      </tr>
      <tr>
          <td><a href="../build-time-vs-runtime-computation-spectrum/">#87 Build-time vs Runtime 計算光譜</a></td>
          <td><strong>同骨 spectrum</strong> — #87 是計算位置光譜（build / runtime / hybrid）+ 四軸判準、本卡是 mitigation 條件光譜（baseline / stretch / trigger）+ 四 context 維度</td>
      </tr>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍是 sanity 防線</a></td>
          <td><strong>scope condition 同骨</strong> — #43 把「scope」變成顯式 fact、本卡把「deployment 條件」變成顯式 fact；都在說「不顯式 = 失控的 default 詮釋」</td>
      </tr>
      <tr>
          <td><a href="../false-sense-of-security-as-primary-failure/">#100 False sense of security 主要失敗模式</a></td>
          <td><strong>#100 的 dimension 3</strong> — context 不寫是 false sense 的第三大產地（dimension 1 = threat model 不對稱 / dimension 2 = mitigation 對位失效 / dimension 3 = context 沒寫）</td>
      </tr>
      <tr>
          <td><a href="../threat-model-explicitness/">#101 Threat model 明確性</a> + <a href="../mitigation-threat-alignment/">#102 Mitigation 對位</a></td>
          <td><strong>本卡是 #101/#102 的 condition 維度</strong> — #101 確立 in-scope threat、#102 確立 mitigation→threat 對位、本卡確立對位在 deployment 條件下的有效性；三者完整定義 mitigation 強度</td>
      </tr>
      <tr>
          <td><a href="../security-teaching-rigor-asymmetry/">#99 資安教學審查標準對應風險不對稱</a></td>
          <td>上游動機 — verifiability-first 的 dimension 3</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>徵兆</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「使用 X」單行 mitigation、沒寫 config / scale / runtime 條件</td>
          <td>補三類條件：成立 / 失效 / deployment 變數</td>
      </tr>
      <tr>
          <td>標準引用（OWASP / RFC）抄整段、沒寫適用 deployment</td>
          <td>標準是 universal-flavored、本地化 deployment context</td>
      </tr>
      <tr>
          <td>Mitigation 描述沒提 work factor / iteration count / 強度參數</td>
          <td>補強度參數 + 對應 actor 能力的 trigger condition</td>
      </tr>
      <tr>
          <td>多實例 / 多區域部署、rate limit / session 描述沒提 distributed</td>
          <td>補多實例 context、明示 local vs distributed 的差異</td>
      </tr>
      <tr>
          <td>「在 modern browser」「在 standard config」沒展開的修飾詞</td>
          <td>列舉 modern / standard 涵蓋什麼、不涵蓋什麼</td>
      </tr>
      <tr>
          <td>Threat actor 能力 / 計算成本沒列</td>
          <td>補 actor model、區分個人 / 組織 / nation-state 的 mitigation 強度</td>
      </tr>
      <tr>
          <td>「之後 deployment 不一樣再說」</td>
          <td>是 <a href="../external-trigger-for-high-roi-work/">#72</a> 結構性跳過、補 trigger</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="適用範圍與邊界">適用範圍與邊界</h2>
<ul>
<li><strong>適用</strong>：資安 mitigation 的所有論述（auth / crypto / 傳輸 / 防護 / scale-sensitive control）；任何「方法有效性受部署條件影響」的領域（concurrency primitive 在不同 memory model / DB transaction 在不同 isolation level / consensus 演算法在不同 network partition 假設）</li>
<li><strong>不適用</strong>：純歷史 / 概念介紹（不教 mitigation deployment）、研究探討（讀者預期自行 explore condition）</li>
<li><strong>邊界</strong>：「Context-dependence 顯式」≠「窮舉所有 deployment 排列組合」——只列 reader 直覺會誤判的 dimension（最常見 deployment 跟最常見變體）、不必涵蓋整個 deployment space；判別準則：「reader 用 default 條件詮釋會不會 silent 失效」——會 → 補 context、不會 → 不必補</li>
<li><strong>過度條件化反例</strong>：每個 mitigation 列 deployment matrix（10 個 dimension × 5 個值 = 50 個 case）、文章變 deployment guide、不是教學；條件描述的投資量級對應 mitigation 在系統的責任比重——核心 control（auth / crypto）值得三類條件完整、輔助 control 只列 baseline + 一個 stretch case 即可</li>
</ul>
<p>本卡是資安 audit 第三個維度（context-dependence）、配 <a href="../threat-model-explicitness/">#101</a> threat model + <a href="../mitigation-threat-alignment/">#102</a> 對位、後續 #104 citation 形成完整 audit dimension 集合。</p>
]]></content:encoded></item></channel></rss>