<?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>Service-Termination on Tarragon</title><link>https://tarrragon.github.io/blog/tags/service-termination/</link><description>Recent content in Service-Termination on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 02 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/service-termination/index.xml" rel="self" type="application/rss+xml"/><item><title>10.4 服務終止的範圍：外部引用的種類與持有者</title><link>https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/</guid><description>&lt;p>這篇整理一個服務要結束、或失去出資時（只終止一個方案或產品線也適用），要盤點的對象：服務發出去、而且現在存在服務控制範圍以外的引用，以及持有這些引用的人。範圍是提供者（經營服務、發出引用的一方）這一側的終止；自己身為租戶離開別人平台的情形見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出：資產線盤點與並行期執行&lt;/a>（上游公告裡的停止新建、唯讀與關閉各代表什麼，見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>），服務仍在、只淘汰一個 API 版本的情形見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation&lt;/a>。&lt;/p>
&lt;h2 id="外部引用的定義與種類">外部引用的定義與種類&lt;/h2>
&lt;p>&lt;strong>外部引用&lt;/strong>指服務發出去、存放在服務以外的地方、之後會被拿來向服務送出請求、或讓別人的系統依賴服務運作的連結、端點、名稱或身分。服務關閉時，自己系統裡的資料可以刪、程式可以停，外部引用卻還在別人的文件、程式與裝置裡，持有者下一次使用時才會發現服務已經不在。&lt;/p>
&lt;p>常見的外部引用有五種，每一種存放的位置不同，持有者能不能自己修改也不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>種類&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;th>存放在哪裡&lt;/th>
 &lt;th>持有者能不能自己改&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>網址&lt;/td>
 &lt;td>短網址、永久連結、&lt;code>&amp;lt;app&amp;gt;.herokuapp.com&lt;/code> 這類提供者網域下的子網域、登記在第三方服務的 webhook 回呼網址與 OAuth redirect URI&lt;/td>
 &lt;td>文件、貼文、印刷品、書籤、別人網站的連結&lt;/td>
 &lt;td>文件與書籤改得了；印刷品改不了；別人網站上的連結與客戶在第三方服務上登記、指向提供者網域的回呼網址，要等對方改；提供者自己在別的服務登記的 OAuth client 與 webhook，由提供者在退場時刪除&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫在別人程式或韌體裡的端點&lt;/td>
 &lt;td>行動 app 的 SDK 連線的 API 主機、智慧家庭集線器連線的雲端服務&lt;/td>
 &lt;td>已經安裝在使用者裝置上的 app 與韌體&lt;/td>
 &lt;td>要開發者發新版、使用者更新；韌體要有線上更新管道才改得了&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>被嵌入的程式碼網址&lt;/td>
 &lt;td>&lt;code>&amp;lt;script src&amp;gt;&lt;/code> 指向的公共 CDN 腳本&lt;/td>
 &lt;td>別人網站的 HTML&lt;/td>
 &lt;td>網站經營者改得了，但要先知道出了事&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>名稱&lt;/td>
 &lt;td>套件名稱與版本、雲端資源的名稱&lt;/td>
 &lt;td>別人的相依性設定檔與 lockfile、DNS 紀錄&lt;/td>
 &lt;td>改得了，但要先知道名稱已經換了主人&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>綁在網域上的身分&lt;/td>
 &lt;td>套件維護者帳號用來收重設密碼信的信箱網域&lt;/td>
 &lt;td>套件登錄服務的帳號資料&lt;/td>
 &lt;td>登錄服務能取消過期網域的信箱驗證；帳號擁有者改得了綁定的信箱，但通常不知道網域已經過期&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>表格裡的例子都有實際案例：Google 在 2025 年修正 goo.gl 短網址的停用計畫時寫明這些連結「embedded in countless documents, videos, posts and more」（&lt;a href="https://blog.google/technology/developers/googl-link-shortening-update/">Google, 2025&lt;/a>）；Parse 的行動 app SDK 連線的是 &lt;code>api.parse.com&lt;/code>，2016 年宣布關閉時，遷移指南把「新版 app 不再依賴 &lt;code>api.parse.com&lt;/code>」列為完成條件之一（&lt;a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12&lt;/a>）；polyfill.io 是十萬個以上網站以 &lt;code>&amp;lt;script&amp;gt;&lt;/code> 嵌入的腳本網址（&lt;a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024&lt;/a>）。綁在網域上的身分這一種，提供者是收信網域的擁有者（例如套件維護者），持有者是把這個信箱存成帳號身分的服務（例如套件登錄服務）。&lt;/p>
&lt;h2 id="持有者的分類直接客戶間接使用者與第三方持有者">持有者的分類：直接客戶、間接使用者與第三方持有者&lt;/h2>
&lt;p>外部引用的持有者依「提供者聯絡得到他們嗎」分成三類，這個分類決定服務結束時通知與補救能到達誰：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>直接客戶&lt;/strong>：在服務上有帳號、付費或註冊過的人。提供者有他們的信箱，公告與匯出工具都能送到他們手上。Parse 上開發 app 的開發者、Heroku 免費方案的帳號持有人屬於這一類；買了提供者硬體、在服務上有帳號的人（Revolv 集線器、Pebble 手錶的買主）也屬於這一類。&lt;/li>
&lt;li>&lt;strong>間接使用者&lt;/strong>：直接客戶的產品或內容的使用者。提供者沒有他們的聯絡方式，只能經由直接客戶轉達。Parse 開發者發布的 app 的使用者屬於這一類：他們手機上的舊版 app 仍連線 &lt;code>api.parse.com&lt;/code>，要等開發者發新版、自己再更新才會切換。&lt;/li>
&lt;li>&lt;strong>第三方持有者&lt;/strong>：和提供者沒有任何關係、只是手上有一條引用的人。點擊 goo.gl 短網址的讀者、嵌入 polyfill.io 的網站、相依某個 npm 套件的下游專案都是。提供者手上沒有他們的名單，能觸及他們的只有引用被使用的那一刻（中間頁、回應裡的訊息、套件登錄服務的棄用警告），以及登錄服務、CDN 業者這類中介者。&lt;/li>
&lt;/ul>
&lt;p>三類的比例決定退場能做到什麼程度。直接客戶佔多數時，公告、匯出與遷移工具能解決大部分問題；第三方持有者佔多數時，提供者能直接用上的主要是引用被使用的那一刻：goo.gl 在 2024 年 8 月起對部分連結先顯示一頁中間頁，告知連結即將停止運作（&lt;a href="https://developers.googleblog.com/en/google-url-shortener-links-will-no-longer-be-available/">Google, 2024&lt;/a>）；對聯絡不到的第三方持有者來說，點擊短網址、經過中間頁的那一刻，是提供者自己唯一碰得到他們的位置。&lt;/p>
&lt;p>API 的消費者還有另外兩種切法：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/api-consumer-shape/" data-link-title="API Consumer Shape（消費者形態）" data-link-desc="「消費者」在契約決策裡指的是行為完全不同的幾種對象——會回報的整合團隊、不會回報的中介、可直接改的內部呼叫端、聯絡不到的外部整合方">API Consumer Shape&lt;/a> 依「會不會回報、改不改得動」分，其中「聯絡不到的外部整合方」大致對應這裡的第三方持有者；&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/consumer-coordinability/" data-link-title="Consumer Coordinability（消費者可協調度）" data-link-desc="「消費者少而可協調」這句話裡的可協調是什麼——列得出名單、發得出通知、握有 SDK、有強制力四項各自成立與否">Consumer Coordinability&lt;/a> 問的「列得出名單、發得出通知」，就是這裡「聯絡得到的程度」。&lt;/p>
&lt;h2 id="外部引用在服務結束後的兩種結局失效與易手">外部引用在服務結束後的兩種結局：失效與易手&lt;/h2>
&lt;p>引用也可以在服務結束後繼續運作：提供者以靜態封存維持（&lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>），或由接手者承接（&lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構&lt;/a>）。沒有人繼續維持時，一條外部引用會走到兩種結局之一，兩者的差別在持有者拿不拿得到失敗的訊號：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效&lt;/strong>：引用指向的服務或資源不再回應，或回應錯誤（網址失效的情形，英文常稱 link rot）。短網址回 404、API 主機連不上、智慧家庭集線器無法運作。持有者至少拿得到一個失敗的訊號。Nest 在 2016 年關閉 Revolv 集線器的雲端服務時，公告寫的是「The Revolv app won&amp;rsquo;t open and the hub won&amp;rsquo;t work」（&lt;a href="http://web.archive.org/web/20160227112105id_/http://revolv.com/">Revolv 官網，Wayback 2016-02-27&lt;/a>）。&lt;/li>
&lt;li>&lt;strong>易手&lt;/strong>：引用指向的網域、名稱或帳號換了主人，引用照常運作，回應的內容卻由新主人決定。持有者拿到的是一個看起來成功的回應，沒有任何訊號。polyfill.io 的網域與 GitHub 帳號在 2024 年 2 月轉給新的經營者，同年 6 月 Sansec 公布這個網域對行動裝置注入惡意程式碼。網域易手時，沒有任何機制直接通知嵌入它的網站，警告只出現在原作者與 Cloudflare、Fastly 的公開文章裡（&lt;a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024&lt;/a>）。PyPI 的 &lt;code>ctx&lt;/code> 套件在 2022 年 5 月被接管，成因是擁有者帳號的信箱網域過期，被別人重新註冊之後用來重設密碼（&lt;a href="https://python-security.readthedocs.io/pypi-vuln/index-2022-05-24-ctx-domain-takeover.html">PyPI 事件報告&lt;/a>）。&lt;/li>
&lt;/ul>
&lt;p>易手的危險在新主人是誰、會做什麼，持有者事先都不知道：持有者原本信任的是提供者，網域到期被陌生人註冊、或在沒有任何公告的情況下賣出之後，回應請求的是持有者沒有選擇過的新主人，而持有者從回應上看不出差別。所以在沒有接手者、也不維持靜態封存的情況下，續約網域但不回應任何內容（讓引用失效），比出售網域或放任它到期更負責任。易手的結果由新主人決定：Insteon 的伺服器在 2022 年 4 月沒有預告就停止，資產在清算中被一群使用者買下，服務因此恢復，但這個結果沒有任何安排保證。事先公告接手者、在條款裡寫明延續範圍的做法稱為公開移交，條件見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構&lt;/a> 的〈交給接手的社群或公司〉。網域與名稱的處置見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理一個服務要結束、或失去出資時（只終止一個方案或產品線也適用），要盤點的對象：服務發出去、而且現在存在服務控制範圍以外的引用，以及持有這些引用的人。範圍是提供者（經營服務、發出引用的一方）這一側的終止；自己身為租戶離開別人平台的情形見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出：資產線盤點與並行期執行</a>（上游公告裡的停止新建、唯讀與關閉各代表什麼，見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>），服務仍在、只淘汰一個 API 版本的情形見 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a>。</p>
<h2 id="外部引用的定義與種類">外部引用的定義與種類</h2>
<p><strong>外部引用</strong>指服務發出去、存放在服務以外的地方、之後會被拿來向服務送出請求、或讓別人的系統依賴服務運作的連結、端點、名稱或身分。服務關閉時，自己系統裡的資料可以刪、程式可以停，外部引用卻還在別人的文件、程式與裝置裡，持有者下一次使用時才會發現服務已經不在。</p>
<p>常見的外部引用有五種，每一種存放的位置不同，持有者能不能自己修改也不同：</p>
<table>
  <thead>
      <tr>
          <th>種類</th>
          <th>例子</th>
          <th>存放在哪裡</th>
          <th>持有者能不能自己改</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>網址</td>
          <td>短網址、永久連結、<code>&lt;app&gt;.herokuapp.com</code> 這類提供者網域下的子網域、登記在第三方服務的 webhook 回呼網址與 OAuth redirect URI</td>
          <td>文件、貼文、印刷品、書籤、別人網站的連結</td>
          <td>文件與書籤改得了；印刷品改不了；別人網站上的連結與客戶在第三方服務上登記、指向提供者網域的回呼網址，要等對方改；提供者自己在別的服務登記的 OAuth client 與 webhook，由提供者在退場時刪除</td>
      </tr>
      <tr>
          <td>寫在別人程式或韌體裡的端點</td>
          <td>行動 app 的 SDK 連線的 API 主機、智慧家庭集線器連線的雲端服務</td>
          <td>已經安裝在使用者裝置上的 app 與韌體</td>
          <td>要開發者發新版、使用者更新；韌體要有線上更新管道才改得了</td>
      </tr>
      <tr>
          <td>被嵌入的程式碼網址</td>
          <td><code>&lt;script src&gt;</code> 指向的公共 CDN 腳本</td>
          <td>別人網站的 HTML</td>
          <td>網站經營者改得了，但要先知道出了事</td>
      </tr>
      <tr>
          <td>名稱</td>
          <td>套件名稱與版本、雲端資源的名稱</td>
          <td>別人的相依性設定檔與 lockfile、DNS 紀錄</td>
          <td>改得了，但要先知道名稱已經換了主人</td>
      </tr>
      <tr>
          <td>綁在網域上的身分</td>
          <td>套件維護者帳號用來收重設密碼信的信箱網域</td>
          <td>套件登錄服務的帳號資料</td>
          <td>登錄服務能取消過期網域的信箱驗證；帳號擁有者改得了綁定的信箱，但通常不知道網域已經過期</td>
      </tr>
  </tbody>
</table>
<p>表格裡的例子都有實際案例：Google 在 2025 年修正 goo.gl 短網址的停用計畫時寫明這些連結「embedded in countless documents, videos, posts and more」（<a href="https://blog.google/technology/developers/googl-link-shortening-update/">Google, 2025</a>）；Parse 的行動 app SDK 連線的是 <code>api.parse.com</code>，2016 年宣布關閉時，遷移指南把「新版 app 不再依賴 <code>api.parse.com</code>」列為完成條件之一（<a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12</a>）；polyfill.io 是十萬個以上網站以 <code>&lt;script&gt;</code> 嵌入的腳本網址（<a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024</a>）。綁在網域上的身分這一種，提供者是收信網域的擁有者（例如套件維護者），持有者是把這個信箱存成帳號身分的服務（例如套件登錄服務）。</p>
<h2 id="持有者的分類直接客戶間接使用者與第三方持有者">持有者的分類：直接客戶、間接使用者與第三方持有者</h2>
<p>外部引用的持有者依「提供者聯絡得到他們嗎」分成三類，這個分類決定服務結束時通知與補救能到達誰：</p>
<ul>
<li><strong>直接客戶</strong>：在服務上有帳號、付費或註冊過的人。提供者有他們的信箱，公告與匯出工具都能送到他們手上。Parse 上開發 app 的開發者、Heroku 免費方案的帳號持有人屬於這一類；買了提供者硬體、在服務上有帳號的人（Revolv 集線器、Pebble 手錶的買主）也屬於這一類。</li>
<li><strong>間接使用者</strong>：直接客戶的產品或內容的使用者。提供者沒有他們的聯絡方式，只能經由直接客戶轉達。Parse 開發者發布的 app 的使用者屬於這一類：他們手機上的舊版 app 仍連線 <code>api.parse.com</code>，要等開發者發新版、自己再更新才會切換。</li>
<li><strong>第三方持有者</strong>：和提供者沒有任何關係、只是手上有一條引用的人。點擊 goo.gl 短網址的讀者、嵌入 polyfill.io 的網站、相依某個 npm 套件的下游專案都是。提供者手上沒有他們的名單，能觸及他們的只有引用被使用的那一刻（中間頁、回應裡的訊息、套件登錄服務的棄用警告），以及登錄服務、CDN 業者這類中介者。</li>
</ul>
<p>三類的比例決定退場能做到什麼程度。直接客戶佔多數時，公告、匯出與遷移工具能解決大部分問題；第三方持有者佔多數時，提供者能直接用上的主要是引用被使用的那一刻：goo.gl 在 2024 年 8 月起對部分連結先顯示一頁中間頁，告知連結即將停止運作（<a href="https://developers.googleblog.com/en/google-url-shortener-links-will-no-longer-be-available/">Google, 2024</a>）；對聯絡不到的第三方持有者來說，點擊短網址、經過中間頁的那一刻，是提供者自己唯一碰得到他們的位置。</p>
<p>API 的消費者還有另外兩種切法：<a href="/blog/backend/knowledge-cards/api-consumer-shape/" data-link-title="API Consumer Shape（消費者形態）" data-link-desc="「消費者」在契約決策裡指的是行為完全不同的幾種對象——會回報的整合團隊、不會回報的中介、可直接改的內部呼叫端、聯絡不到的外部整合方">API Consumer Shape</a> 依「會不會回報、改不改得動」分，其中「聯絡不到的外部整合方」大致對應這裡的第三方持有者；<a href="/blog/backend/knowledge-cards/consumer-coordinability/" data-link-title="Consumer Coordinability（消費者可協調度）" data-link-desc="「消費者少而可協調」這句話裡的可協調是什麼——列得出名單、發得出通知、握有 SDK、有強制力四項各自成立與否">Consumer Coordinability</a> 問的「列得出名單、發得出通知」，就是這裡「聯絡得到的程度」。</p>
<h2 id="外部引用在服務結束後的兩種結局失效與易手">外部引用在服務結束後的兩種結局：失效與易手</h2>
<p>引用也可以在服務結束後繼續運作：提供者以靜態封存維持（<a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>），或由接手者承接（<a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a>）。沒有人繼續維持時，一條外部引用會走到兩種結局之一，兩者的差別在持有者拿不拿得到失敗的訊號：</p>
<ul>
<li><strong>失效</strong>：引用指向的服務或資源不再回應，或回應錯誤（網址失效的情形，英文常稱 link rot）。短網址回 404、API 主機連不上、智慧家庭集線器無法運作。持有者至少拿得到一個失敗的訊號。Nest 在 2016 年關閉 Revolv 集線器的雲端服務時，公告寫的是「The Revolv app won&rsquo;t open and the hub won&rsquo;t work」（<a href="http://web.archive.org/web/20160227112105id_/http://revolv.com/">Revolv 官網，Wayback 2016-02-27</a>）。</li>
<li><strong>易手</strong>：引用指向的網域、名稱或帳號換了主人，引用照常運作，回應的內容卻由新主人決定。持有者拿到的是一個看起來成功的回應，沒有任何訊號。polyfill.io 的網域與 GitHub 帳號在 2024 年 2 月轉給新的經營者，同年 6 月 Sansec 公布這個網域對行動裝置注入惡意程式碼。網域易手時，沒有任何機制直接通知嵌入它的網站，警告只出現在原作者與 Cloudflare、Fastly 的公開文章裡（<a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024</a>）。PyPI 的 <code>ctx</code> 套件在 2022 年 5 月被接管，成因是擁有者帳號的信箱網域過期，被別人重新註冊之後用來重設密碼（<a href="https://python-security.readthedocs.io/pypi-vuln/index-2022-05-24-ctx-domain-takeover.html">PyPI 事件報告</a>）。</li>
</ul>
<p>易手的危險在新主人是誰、會做什麼，持有者事先都不知道：持有者原本信任的是提供者，網域到期被陌生人註冊、或在沒有任何公告的情況下賣出之後，回應請求的是持有者沒有選擇過的新主人，而持有者從回應上看不出差別。所以在沒有接手者、也不維持靜態封存的情況下，續約網域但不回應任何內容（讓引用失效），比出售網域或放任它到期更負責任。易手的結果由新主人決定：Insteon 的伺服器在 2022 年 4 月沒有預告就停止，資產在清算中被一群使用者買下，服務因此恢復，但這個結果沒有任何安排保證。事先公告接手者、在條款裡寫明延續範圍的做法稱為公開移交，條件見 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a> 的〈交給接手的社群或公司〉。網域與名稱的處置見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a>。</p>
<h2 id="盤點每一種外部引用的欄位">盤點每一種外部引用的欄位</h2>
<p>一種外部引用在服務結束時的下場，由下列幾項性質決定；盤點就是對每一種引用記下這幾項：</p>
<ul>
<li><strong>存放位置</strong>：引用寫在哪裡（文件、別人的程式、韌體、DNS 紀錄、相依性設定檔）。</li>
<li><strong>持有者的類別</strong>：直接客戶、間接使用者還是第三方持有者，以及各佔多少，比較的單位是請求量：請求帶的存取憑證先分出帶直接客戶憑證的請求與沒有憑證的請求（後者多半來自第三方持有者）；帶直接客戶憑證的請求，再用客戶端類型或 User-Agent 分出直接客戶自己的後端與間接使用者的裝置（識別符的分層見 <a href="/blog/backend/11-api-design/consumer-usage-observability/" data-link-title="11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界" data-link-desc="對外契約的變更決定各自需要的用量證據：觀測要建到什麼粒度、消費者身分怎麼認、哪些維度要全量常設、哪些可以抽樣或開取樣窗口，以及固定形狀 REST 拿不到欄位級用量的替代做法">11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界</a>）；客戶端版本的分布則顯示舊版還有多少。</li>
<li><strong>持有者能不能自己改</strong>：能改的，退場的工作是通知與提供替代；不能改的（印刷品、韌體），只能讓引用本身繼續運作或明確失效。</li>
<li><strong>還有沒有人在用</strong>：服務有沒有使用紀錄可以回答這個問題。goo.gl 在 2025 年修正停用計畫時，改成只停用「2024 年底沒有活動」的連結，其餘保留，依據的就是點擊紀錄（<a href="https://blog.google/technology/developers/googl-link-shortening-update/">Google, 2025</a>）；npm 判斷一個套件能不能下架時，看的是登錄服務記錄的相依關係與下載數（<a href="https://docs.npmjs.com/policies/unpublish">npm unpublish 政策</a>）。沒有使用紀錄的服務，結束時分不出哪些引用還活著。</li>
<li><strong>失效與易手各會造成什麼</strong>：持有者拿到錯誤時會發生什麼，引用換了主人時會發生什麼。</li>
<li><strong>讓引用繼續運作所需的資料與格式</strong>：對照表、套件檔案、設定這類資料，有沒有一份別人讀得懂的匯出格式（見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 的〈使用紀錄與可交出的資料格式〉）。</li>
<li><strong>結束時一定會失去的部分</strong>：提供者靠著自己與第三方的契約或授權才提供得了、因此無法交給別人的功能（見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 的〈依賴第三方契約與授權而無法移交的部分〉）。</li>
</ul>
<p>到宣布結束時多半已經來不及改的是存放位置（取決於端點間接層與命名空間的選擇）、持有者能不能自己改、還有沒有人在用的紀錄（持有者類別的推估也靠它，要在服務運作期間累積）、讓引用繼續運作所需的資料格式，以及結束時一定會失去的部分，這些是 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 要在上線時處理的；客戶端還收得到更新時，端點間接層與本地運作模式仍可補上，條件寫在 10.5〈客戶端讀取服務位置的端點間接層〉。持有者類別與失效、易手的後果是盤點時記下的事實；結束時還能改變的是引用的去向，見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a> 與 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a>。</p>
<h2 id="短網址服務的外部引用">短網址服務的外部引用</h2>
<p>本篇的分類在短網址服務裡全部出現：短網址本身是網址，持有者幾乎全是第三方持有者（<a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格</a> 的〈連結的保存期限與保存承諾〉），服務網域是所有引用共用的命名空間，對照表是讓引用繼續運作所需的全部資料。短網址服務的價值全部在外部引用上，而那些引用的持有者幾乎都聯絡不到。誰在出資維持轉址、出資方式怎麼影響連結的存續，見 <a href="/blog/business/case-analyses/url-shortener-business-models/" data-link-title="短網址的商業模型：訂閱、自架、平台內建與廣告插頁各由誰出資維持轉址" data-link-desc="拆解維持短網址轉址的錢從哪裡來：建立連結的人為點擊分析、品牌網域或託管服務付訂閱費，自架者自己付主機與網域，平台由母公司出資，廣告插頁由廣告主出資；連結能活多久跟著出資者走，各出資方式交給工程的規格也不同">短網址的商業模型：訂閱、自架、平台內建與廣告插頁各由誰出資維持轉址</a>。</p>
]]></content:encoded></item><item><title>10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</title><link>https://tarrragon.github.io/blog/backend/10-system-evolution/exit-ready-design/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/10-system-evolution/exit-ready-design/</guid><description>&lt;p>服務上線時做的幾個設計決定，在服務結束時決定了能不能讓外部引用繼續運作、能不能把服務交給別人。外部引用指服務發出去、存在別人的文件、程式與裝置裡的網址、端點、名稱與身分，持有者是手上有這些引用的人，完整定義見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者&lt;/a>。這篇整理這些決定。它們在上線時的成本多半很低，越接近結束越難補上：引用早就寫進別人的程式、韌體與文件裡，要改只能靠客戶端更新，而更新要在服務還在運作時送到裝置上。&lt;/p>
&lt;h2 id="客戶端讀取服務位置的端點間接層">客戶端讀取服務位置的端點間接層&lt;/h2>
&lt;p>&lt;strong>端點間接層&lt;/strong>（英文常見的說法是 bootstrap config）指客戶端（行動 app、裝置韌體、SDK）不直接寫死它要連線的伺服器位置，而是先讀一份設定，再依設定連線；完整的間接層還要讓這份設定本身的來源可以由提供者以外的人（使用者或接手者）改寫，下面 Pebble 的案例說明為什麼兩件都要做。有沒有這一層，決定服務結束時能不能讓已經發出去的客戶端改連到別的伺服器。&lt;/p>
&lt;p>Parse 與 Pebble 兩個案例的差別，在改寫連線位置要不要經過發布新版 app。Parse 在 2016 年 1 月宣布關閉時，同一天釋出開源的 Parse Server，並寫道「Our client SDKs now support changing the API server location to direct them to your own」（&lt;a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/">Parse, 2016&lt;/a>）。要讓已經安裝在使用者手機上的 app 改連自架的伺服器，開發者得發布帶新設定的 app 版本，使用者得更新；遷移指南直接說發布新版、讓客戶端改連自架伺服器這一階段「is non-trivial, and will require dedicated development time」（&lt;a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12&lt;/a>）。&lt;/p>
&lt;p>Pebble 的手機 app 讀取的是一份服務設定（boot config），裡面列出各項雲端服務的位置。2017 年 4 月的更新開放使用者用一個連結改寫這份設定的來源：「The link pebble://custom-boot-config-url/CUSTOM_URL forces the Pebble mobile app to load its service configuration from the specified CUSTOM_URL」（&lt;a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2017/04/04/transitioning-update/">Pebble 開發者部落格, 2017&lt;/a>）。2018 年 Fitbit 停止 Pebble 的雲端服務時，社群專案 Rebble 接手，切換方式是讓使用者在手機上點一個連結（Rebble 的公告沒有寫出背後用的是哪個機制，推測就是這個改寫設定來源的連結），「you have nothing to download or install」（&lt;a href="https://rebble.io/2018/06/13/get-ready-to-rebble.html">Rebble, 2018&lt;/a>）。Pebble 2017 年的公告只寫了使用者可以改寫設定的來源；手機 app 讀取服務設定這一層是不是從 Pebble 手錶上市時就存在，公告沒有交代。這個案例也說明，改寫設定來源的能力可以晚於上線補上，條件是補上它的那一版客戶端，要在提供者的發布管道與更新伺服器還在運作時送到足夠多的裝置上；Pebble 的這次更新比雲端關閉早了一年多。沒有更新管道、或更新管道已經停止的客戶端（不支援線上更新的韌體、停止發版的 app）沒有這條路；支援線上更新（OTA）的韌體和 app 適用同一個條件。寫死在客戶端的主機名稱本身也是一層間接：網域移交出去，接手者就能把它指到新的伺服器。客戶端信任公開的憑證機構時，接手者用網域就能重新申請數位憑證；只有客戶端釘選了數位憑證或自帶信任錨點時，才需要提供者交出金鑰或換錨點的路徑。&lt;/p>
&lt;p>所以設計上要預留的是兩件事（寫死的主機名稱落在可以移交的網域上，是兩件都沒有時的後備）：客戶端連線的位置來自一份可以更新的設定，而不是編譯進程式的常數；以及這份設定的來源，在提供者不再運作時有辦法被指到接手者的伺服器。只做到「設定可以更新」這一件，存放設定的伺服器本身仍是提供者的服務，它關閉時客戶端就停在最後一份設定上。另外還有客戶端信任的對象要預留：客戶端若只接受提供者數位憑證的連線、或只安裝提供者金鑰簽章的韌體與設定，接手者即使改寫了設定來源，也要拿到這些金鑰，或有一條換掉信任錨點的路徑，才連得上、推得出更新。&lt;/p>
&lt;h2 id="雲端關閉後的裝置功能本地運作模式">雲端關閉後的裝置功能：本地運作模式&lt;/h2>
&lt;p>&lt;strong>本地運作模式&lt;/strong>（英文多稱 local control 或 offline mode）指裝置在連不上提供者的雲端服務時，仍能完成它被買來做的那件事，例如開關燈、感測器觸發其他裝置。對賣出去的硬體來說，有沒有這個模式決定雲端關閉時裝置是少了一些功能，還是整台失效（英文報導多稱為 bricking）。它和雲端架構裡的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/static-stability/" data-link-title="Static Stability" data-link-desc="控制面失效時資料面用快取的已知好配置繼續服務的設計模式">Static Stability&lt;/a> 是同一個想法：控制面失效時，資料面照最後的狀態繼續運作。&lt;/p>
&lt;p>Insteon 與 Revolv 分別是有本地模式與沒有本地模式的結局。Insteon 的開關、插座與感測器之間用本地的專有無線協定連動。2022 年 4 月經營公司停止營運、伺服器關閉時，「All of those stayed operational, but users couldn&amp;rsquo;t remotely access their hub through the app or program the hub」（&lt;a href="https://staceyoniot.com/users-report-insteons-servers-are-back-online/">Stacey on IoT, 2022&lt;/a>）：失去的是 app 遠端控制與雲端整合，家裡的燈照樣開關。Revolv 的集線器沒有本地模式，Nest 在 2016 年關閉它的雲端服務時，集線器整台無法運作，Nest 能給的補救只剩退款，而退款是公告一個多月後才加上的（見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a> 的〈賣出去的裝置的補償〉）。美國聯邦貿易委員會（FTC）調查 Revolv 的關閉後寫道，擔心的是「reasonable consumers would not expect the Revolv hubs to become unusable」（&lt;a href="https://www.ftc.gov/system/files/documents/closing_letters/nid/160707nestrevolvletter.pdf">FTC 結案信, 2016&lt;/a>）。&lt;/p>
&lt;p>本地模式也能在結束前補上，但要提前很久做。Fitbit 在 2016 年 12 月接手 Pebble 之後，第一步是更新手機 app，「loosening their dependency on a patchwork of cloud services」（&lt;a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2016/12/14/first-steps-forward-with-fitbit/">Pebble 開發者部落格, 2016&lt;/a>）：2017 年 4 月的版本在連不上認證與更新伺服器時仍可使用，並加入離線模式（&lt;a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2017/04/04/transitioning-update/">Pebble 開發者部落格, 2017&lt;/a>）。這次 app 更新比 2018 年 6 月雲端關閉早了一年多。Spotify 的車用控制器 Car Thing 則沒有本地模式，2024 年停止支援時，支援頁建議使用者回復原廠設定，並依電子廢棄物規定處理裝置（&lt;a href="http://web.archive.org/web/20240523162707id_/https://support.spotify.com/us/article/car-thing-discontinued/">Spotify 支援頁，Wayback 2024-05-23&lt;/a>）。&lt;/p></description><content:encoded><![CDATA[<p>服務上線時做的幾個設計決定，在服務結束時決定了能不能讓外部引用繼續運作、能不能把服務交給別人。外部引用指服務發出去、存在別人的文件、程式與裝置裡的網址、端點、名稱與身分，持有者是手上有這些引用的人，完整定義見 <a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a>。這篇整理這些決定。它們在上線時的成本多半很低，越接近結束越難補上：引用早就寫進別人的程式、韌體與文件裡，要改只能靠客戶端更新，而更新要在服務還在運作時送到裝置上。</p>
<h2 id="客戶端讀取服務位置的端點間接層">客戶端讀取服務位置的端點間接層</h2>
<p><strong>端點間接層</strong>（英文常見的說法是 bootstrap config）指客戶端（行動 app、裝置韌體、SDK）不直接寫死它要連線的伺服器位置，而是先讀一份設定，再依設定連線；完整的間接層還要讓這份設定本身的來源可以由提供者以外的人（使用者或接手者）改寫，下面 Pebble 的案例說明為什麼兩件都要做。有沒有這一層，決定服務結束時能不能讓已經發出去的客戶端改連到別的伺服器。</p>
<p>Parse 與 Pebble 兩個案例的差別，在改寫連線位置要不要經過發布新版 app。Parse 在 2016 年 1 月宣布關閉時，同一天釋出開源的 Parse Server，並寫道「Our client SDKs now support changing the API server location to direct them to your own」（<a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/">Parse, 2016</a>）。要讓已經安裝在使用者手機上的 app 改連自架的伺服器，開發者得發布帶新設定的 app 版本，使用者得更新；遷移指南直接說發布新版、讓客戶端改連自架伺服器這一階段「is non-trivial, and will require dedicated development time」（<a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12</a>）。</p>
<p>Pebble 的手機 app 讀取的是一份服務設定（boot config），裡面列出各項雲端服務的位置。2017 年 4 月的更新開放使用者用一個連結改寫這份設定的來源：「The link pebble://custom-boot-config-url/CUSTOM_URL forces the Pebble mobile app to load its service configuration from the specified CUSTOM_URL」（<a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2017/04/04/transitioning-update/">Pebble 開發者部落格, 2017</a>）。2018 年 Fitbit 停止 Pebble 的雲端服務時，社群專案 Rebble 接手，切換方式是讓使用者在手機上點一個連結（Rebble 的公告沒有寫出背後用的是哪個機制，推測就是這個改寫設定來源的連結），「you have nothing to download or install」（<a href="https://rebble.io/2018/06/13/get-ready-to-rebble.html">Rebble, 2018</a>）。Pebble 2017 年的公告只寫了使用者可以改寫設定的來源；手機 app 讀取服務設定這一層是不是從 Pebble 手錶上市時就存在，公告沒有交代。這個案例也說明，改寫設定來源的能力可以晚於上線補上，條件是補上它的那一版客戶端，要在提供者的發布管道與更新伺服器還在運作時送到足夠多的裝置上；Pebble 的這次更新比雲端關閉早了一年多。沒有更新管道、或更新管道已經停止的客戶端（不支援線上更新的韌體、停止發版的 app）沒有這條路；支援線上更新（OTA）的韌體和 app 適用同一個條件。寫死在客戶端的主機名稱本身也是一層間接：網域移交出去，接手者就能把它指到新的伺服器。客戶端信任公開的憑證機構時，接手者用網域就能重新申請數位憑證；只有客戶端釘選了數位憑證或自帶信任錨點時，才需要提供者交出金鑰或換錨點的路徑。</p>
<p>所以設計上要預留的是兩件事（寫死的主機名稱落在可以移交的網域上，是兩件都沒有時的後備）：客戶端連線的位置來自一份可以更新的設定，而不是編譯進程式的常數；以及這份設定的來源，在提供者不再運作時有辦法被指到接手者的伺服器。只做到「設定可以更新」這一件，存放設定的伺服器本身仍是提供者的服務，它關閉時客戶端就停在最後一份設定上。另外還有客戶端信任的對象要預留：客戶端若只接受提供者數位憑證的連線、或只安裝提供者金鑰簽章的韌體與設定，接手者即使改寫了設定來源，也要拿到這些金鑰，或有一條換掉信任錨點的路徑，才連得上、推得出更新。</p>
<h2 id="雲端關閉後的裝置功能本地運作模式">雲端關閉後的裝置功能：本地運作模式</h2>
<p><strong>本地運作模式</strong>（英文多稱 local control 或 offline mode）指裝置在連不上提供者的雲端服務時，仍能完成它被買來做的那件事，例如開關燈、感測器觸發其他裝置。對賣出去的硬體來說，有沒有這個模式決定雲端關閉時裝置是少了一些功能，還是整台失效（英文報導多稱為 bricking）。它和雲端架構裡的 <a href="/blog/backend/knowledge-cards/static-stability/" data-link-title="Static Stability" data-link-desc="控制面失效時資料面用快取的已知好配置繼續服務的設計模式">Static Stability</a> 是同一個想法：控制面失效時，資料面照最後的狀態繼續運作。</p>
<p>Insteon 與 Revolv 分別是有本地模式與沒有本地模式的結局。Insteon 的開關、插座與感測器之間用本地的專有無線協定連動。2022 年 4 月經營公司停止營運、伺服器關閉時，「All of those stayed operational, but users couldn&rsquo;t remotely access their hub through the app or program the hub」（<a href="https://staceyoniot.com/users-report-insteons-servers-are-back-online/">Stacey on IoT, 2022</a>）：失去的是 app 遠端控制與雲端整合，家裡的燈照樣開關。Revolv 的集線器沒有本地模式，Nest 在 2016 年關閉它的雲端服務時，集線器整台無法運作，Nest 能給的補救只剩退款，而退款是公告一個多月後才加上的（見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a> 的〈賣出去的裝置的補償〉）。美國聯邦貿易委員會（FTC）調查 Revolv 的關閉後寫道，擔心的是「reasonable consumers would not expect the Revolv hubs to become unusable」（<a href="https://www.ftc.gov/system/files/documents/closing_letters/nid/160707nestrevolvletter.pdf">FTC 結案信, 2016</a>）。</p>
<p>本地模式也能在結束前補上，但要提前很久做。Fitbit 在 2016 年 12 月接手 Pebble 之後，第一步是更新手機 app，「loosening their dependency on a patchwork of cloud services」（<a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2016/12/14/first-steps-forward-with-fitbit/">Pebble 開發者部落格, 2016</a>）：2017 年 4 月的版本在連不上認證與更新伺服器時仍可使用，並加入離線模式（<a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2017/04/04/transitioning-update/">Pebble 開發者部落格, 2017</a>）。這次 app 更新比 2018 年 6 月雲端關閉早了一年多。Spotify 的車用控制器 Car Thing 則沒有本地模式，2024 年停止支援時，支援頁建議使用者回復原廠設定，並依電子廢棄物規定處理裝置（<a href="http://web.archive.org/web/20240523162707id_/https://support.spotify.com/us/article/car-thing-discontinued/">Spotify 支援頁，Wayback 2024-05-23</a>）。</p>
<p>設計時要分清楚哪些功能本質上需要雲端（跨裝置同步、遠端存取、第三方整合），哪些只是實作時方便放在雲端（排程、裝置之間的連動、本機設定）。後一類功能在上線時就放在本地執行，雲端關閉時裝置保留它被買來做的那些事。</p>
<h2 id="外部引用所屬的命名空間提供者網域客戶網域與共用登錄服務">外部引用所屬的命名空間：提供者網域、客戶網域與共用登錄服務</h2>
<p><strong>命名空間</strong>在這裡指分配名稱、並決定名稱指向哪裡的那一層。外部引用的網址、主機名稱或名稱都屬於某個命名空間：提供者的網域、客戶自己的網域、或某個共用的登錄服務。服務結束時，引用能不能被保住，取決於誰還控制那個命名空間。</p>
<p>落在提供者網域下的引用，提供者停止續約或出售網域，引用就跟著失效或易手。Firebase Dynamic Links 在 2025 年關閉時，常見問答回答使用者能不能保留或轉移 <code>page.link</code> 網域：「No, once the Firebase Dynamic Links service is shut down any .page.link domains will no longer be available」（<a href="https://firebase.google.com/support/dynamic-links-faq">Firebase 常見問答</a>）。Firebase Dynamic Links 也讓客戶改用自己的網域；用自己網域的客戶，服務關閉後還能把網域指到別的轉址服務。短網址服務提供自訂網域的理由之一就在這裡，見 <a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格</a> 的〈自訂網域與網域名聲的隔離〉；客戶網域讓連結能搬到別的服務，這件事的商業分析，見 <a href="/blog/business/case-analyses/url-shortener-business-models/" data-link-title="短網址的商業模型：訂閱、自架、平台內建與廣告插頁各由誰出資維持轉址" data-link-desc="拆解維持短網址轉址的錢從哪裡來：建立連結的人為點擊分析、品牌網域或託管服務付訂閱費，自架者自己付主機與網域，平台由母公司出資，廣告插頁由廣告主出資；連結能活多久跟著出資者走，各出資方式交給工程的規格也不同">短網址的商業模型：訂閱、自架、平台內建與廣告插頁各由誰出資維持轉址</a> 的〈網域：品牌短網址的定價與切換成本〉。</p>
<p>落在共用命名空間的名稱，要看名稱釋出之後能不能被別人申請。Azure App Service 舊的預設主機名 <code>&lt;應用程式名稱&gt;.azurewebsites.net</code>，Microsoft 自己的文件形容為「globally predictable」；新的主機名加上一段隨機雜湊，外人無法重建同名的主機，而同一份文件寫明這種雜湊主機名「You can&rsquo;t apply them to existing resources retroactively」（<a href="https://learn.microsoft.com/en-us/azure/app-service/reference-dangling-subdomain-prevention">Microsoft Learn</a>）。名稱可預測的風險在於：客戶網域裡指向這個主機名的 CNAME，若在資源刪除後沒有一起刪掉，別人重新申請同名的主機，就會接收客戶那個子網域的流量，稱為懸空 DNS 與子網域接管。雜湊主機名這項補救只涵蓋之後新建的資源；名稱已經以可預測的方式發出去的既有資源，要靠網域驗證或偵測懸空 DNS 紀錄這類另外的手段。名稱的保留與回收、子網域接管的細節見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a>。</p>
<p>被嵌入的程式碼另有一層提供者做得到的設計：發布帶版本號、內容固定的網址，嵌入者就能加上子資源完整性（Subresource Integrity，SRI）雜湊。跨來源載入的腳本要比對雜湊，提供者的回應還要帶 CORS 標頭（<code>Access-Control-Allow-Origin</code>），嵌入者在 <code>&lt;script&gt;</code> 加上 <code>crossorigin</code> 屬性。網域易手、內容被換掉之後，瀏覽器比對雜湊不符會拒絕載入，引用的結局從易手變成失效。</p>
<h2 id="使用紀錄與可交出的資料格式">使用紀錄與可交出的資料格式</h2>
<p>服務結束時有兩個問題要靠上線時就存在的資料回答：哪些外部引用還有人在用，以及讓引用繼續運作需要哪些資料、能用什麼格式交出去。</p>
<p><strong>使用紀錄</strong>回答哪些外部引用還有人在用。記錄的單位與做法（最後使用時間表、消費者身分與客戶端版本的識別）見 <a href="/blog/backend/11-api-design/consumer-usage-observability/" data-link-title="11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界" data-link-desc="對外契約的變更決定各自需要的用量證據：觀測要建到什麼粒度、消費者身分怎麼認、哪些維度要全量常設、哪些可以抽樣或開取樣窗口，以及固定形狀 REST 拿不到欄位級用量的替代做法">11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界</a>；客戶端版本與存取憑證是事後推估持有者類別、決定公告期的依據，所以和最後使用時間一起記。goo.gl 在 2024 年宣布所有連結將回 404，理由之一是「more than 99% of them had no activity in the last month」；2025 年修正為只停用 2024 年底沒有活動的連結（<a href="https://developers.googleblog.com/en/google-url-shortener-links-will-no-longer-be-available/">Google, 2024</a>；<a href="https://blog.google/technology/developers/googl-link-shortening-update/">Google, 2025</a>）。兩個決定都建立在點擊紀錄上；沒有點擊紀錄，提供者只能在「全部保留」與「全部停用」之間選。紀錄要保存到什麼粒度，同時受隱私要求限制：只需要知道「這條引用最近有沒有被使用」時，保存彙總後的最後使用時間就夠了。</p>
<p><strong>可交出的資料格式</strong>回答讓引用繼續運作所需的資料能不能交出去。讓外部引用繼續運作所需的資料（短網址的對照表、套件的檔案與中繼資料、專案的原始碼與 issue）要能匯出成別人讀得懂的格式。2009 年由短網址業者組成、Internet Archive 管理的封存計畫 301Works，參與條款寫明對照表的格式是以 tab 分隔的長網址、短網址與選填的點擊數（<a href="https://web.archive.org/web/20091116124902/http://www.301works.org/post/220152694/terms-of-participation">301Works 參與條款，Wayback 2009</a>）；Microsoft 在 2017 年關閉 CodePlex 時，提供 Markdown 與 JSON 格式的專案封存檔（<a href="https://devblogs.microsoft.com/bharry/shutting-down-codeplex/">Microsoft, 2017</a>）。客戶自己使用者的聯絡資料也在要能匯出的範圍裡：提供者聯絡不到間接使用者，只能靠客戶轉達，客戶手上有沒有這份名單，決定轉達做不做得到（見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a> 的〈公告期的長度與通知管道〉）。格式在上線時就定下來，結束時只要執行匯出；結束時才設計格式，就要在最缺人力的時候設計並實作一套新的匯出格式。交給誰、交出去之後怎麼用，見 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a>。</p>
<h2 id="依賴第三方契約與授權而無法移交的部分">依賴第三方契約與授權而無法移交的部分</h2>
<p>有些功能在服務結束時無法交給別人，原因在它們依賴提供者和第三方之間的契約或授權，缺的並不是資料或程式碼。這些功能能不能移交，在提供者選用那個第三方服務、簽下契約的時候就決定了。</p>
<p>Rebble 接手 Pebble 的雲端服務時，有兩項功能接不走。一是時間軸的即時推送：「for technical reasons it is impossible for any entity other than Fitbit to provide that service」。二是 iOS 上回覆簡訊的功能：「Pebble had agreements in place with several carriers and service providers in order to provide the ability to reply to text messages on iOS」（<a href="https://rebble.io/2018/02/15/rebble-web-services.html">Rebble, 2018</a>）。程式碼也有同樣的限制：Google 在 2025 年釋出 PebbleOS 原始碼時，移除了部分專有程式碼，主要是晶片支援與藍牙堆疊，所以釋出的版本「will not compile or link as released」（<a href="https://opensource.googleblog.com/2025/01/see-code-that-powered-pebble-smartwatches.html">Google Open Source Blog, 2025</a>）。</p>
<p>條款不允許移交的功能，屬於 <a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a> 盤點清單裡「結束時一定會失去的部分」那一項，它們在結束時的處置見 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a> 的〈交不出去的功能與引用：第三方契約、授權限制與提供者網域〉。</p>
<h2 id="租戶的可遷出保險清單與提供者的退場設計">租戶的可遷出保險清單與提供者的退場設計</h2>
<p><a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型：從全託管到自建的光譜與邊界</a> 列了一份「可遷出保險清單」，給租戶（在別人的平台上經營服務的一方）用。清單裡的自有網域、資料定期匯出、客戶聯絡管道自有三項，是租戶在採用別人的平台時替自己買的保險。這篇的設計是同一份保險從提供者這一側看：讓客戶能用自己的網域、讓資料能匯出、讓客戶能匯出自己使用者的聯絡資料，分別對應清單的三項；讓客戶端的連線位置能被改寫，則是租戶清單沒有、只有提供者做得到的一項。提供者在上線時做了這些，等於替所有持有外部引用的人買了同一份保險，包括那些不在合約裡、也不會替自己買保險的第三方持有者。清單另外三項的提供者側各不相同：金流可攜靠的是讓扣款授權綁在客戶自己名下的金流帳戶，或配合金流商之間的卡號資料轉移，授權本身不在提供者的匯出檔裡；密碼不可攜的預案是讓密碼雜湊連同演算法、參數與 pepper（如果有）一起匯出；業務邏輯文件化對應的是讓設定與規則能匯出。租戶側的處理見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出：資產線盤點與並行期執行</a> 的〈身分線〉與〈整合線〉；這些都是 <a href="/blog/backend/knowledge-cards/vendor-lock-in/" data-link-title="Vendor Lock-In" data-link-desc="說明採用供應商產品後，其 API 與格式滲入程式碼造成的退出成本">Vendor Lock-in</a> 在提供者這一側的解法。</p>
]]></content:encoded></item><item><title>10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</title><link>https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/</guid><description>&lt;p>本篇的範圍是服務結束時網域與名稱的處置。外部引用（服務發出去、存在別人手上的網址、端點、名稱與身分）在服務結束後有兩種結局：失效是引用不再得到回應，易手是引用指向的網域或名稱換了主人、回應由新主人決定（定義見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者&lt;/a>）；決定走向哪一種的，是引用所在的網域或名稱之後歸誰。上線時怎麼選命名空間見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間&lt;/a>，這篇處理結束時已經發出去、存在別人手上的那些網域與名稱。&lt;/p>
&lt;h2 id="網域在服務結束後的去向續約並回應續約不回應到期出售">網域在服務結束後的去向：續約並回應、續約不回應、到期、出售&lt;/h2>
&lt;p>服務停止營運之後，它的網域有下列幾種去向，差別在引用會繼續運作、失效還是易手：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>繼續續約、維持轉址或靜態內容&lt;/strong>：引用繼續運作。成本只剩網域續約與一個能回應請求的最小服務，做法見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>。&lt;/li>
&lt;li>&lt;strong>繼續續約、但不回應任何內容&lt;/strong>：引用失效，但不會易手。在不讓網域落到別人手上的做法裡，這是成本最低的一種。&lt;/li>
&lt;li>&lt;strong>讓網域到期&lt;/strong>：網域釋出之後任何人都能註冊，引用可能易手。&lt;/li>
&lt;li>&lt;strong>出售或轉讓&lt;/strong>：引用確定易手。沒有任何公告的出售，新主人可以照常提供服務，也可以不；公開移交（事先公告接手者、在條款裡寫明延續範圍，條件見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構&lt;/a> 的〈交給接手的社群或公司〉）讓持有者事先知道新主人是誰，但條款本身沒有被執行的機制。&lt;/li>
&lt;/ul>
&lt;p>讓網域到期與沒有公告的出售，對持有者的後果相同：持有者手上的引用照常運作，回應的內容卻由持有者不知道的新主人決定。所以網域的去向要在退場計畫裡明寫，而不是留給公司解散時的資產處分程序決定。短網址服務的需求定義也把網域的去向列進保存承諾：服務結束營運時，「網域在停止營運後由誰續約」是保存承諾的一部分（&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格&lt;/a> 的〈連結的保存期限與保存承諾〉）。&lt;/p>
&lt;p>「續約但不回應」要在網域的每一層都做到不回應。網域沒有設定內容時，有些註冊商會預設掛上停放頁並放廣告，回應的內容就變成註冊商決定的，所以要到註冊商的網域設定裡確認停放功能是關的。網域上原本收信的 MX 紀錄也要決定去向：移除它讓寄往舊信箱的信退回，或保留一個有人看管的信箱，讓重設密碼信與客戶的支援信都有人處理。指向雲端 IP 的 A／AAAA 紀錄與萬用字元紀錄也要刪除，雲端業者把那個 IP 分給別人之後，紀錄就會懸空。網域不再寄信時，可以發布表明這個網域不寄信的 SPF 與 DMARC 紀錄，讓別人冒用網域寄出的信被收件端拒收。&lt;/p>
&lt;p>續約要持續多久，要分兩種網域判斷。曾經被當成帳號信箱使用的網域，放手的條件是綁在這個網域上的帳號全部改綁到別的信箱，這要另外盤點，和流量無關：本篇〈綁在可過期網域上的帳號身分〉一節的 &lt;code>ctx&lt;/code> 事件，被重新註冊的就是一個閒置的網域。其他網域看還有沒有引用在被使用：網域釋出之後，註冊它的人能接收所有仍在發出的請求，所以放手的風險隨著請求量與被嵌入的程度上升。判斷請求量需要一份紀錄。掃描器與爬蟲的請求要從請求本身（User-Agent、請求的路徑）排除，這只有 HTTP 存取紀錄做得到，所以判斷要在還保有存取紀錄的靜態封存階段做（見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>）；關閉之後只剩自己名稱伺服器的查詢紀錄，它看到的是解析器而不是發出請求的人，分不出掃描器，解析器的快取又讓數字偏低，只能當趨勢參考，不能用來判定歸零。沒有這份紀錄、或請求一直沒有歸零時，續約是一筆沒有終點的固定成本，要在退場計畫裡當成長期支出編列；多數註冊商允許一次續約多年，可以在公司還有資金時先付清，並寫明之後由誰接著付。&lt;/p>
&lt;h2 id="網域的出售polyfillio-的經過">網域的出售：polyfill.io 的經過&lt;/h2>
&lt;p>polyfill.io 是一個公共 CDN 腳本，網站以 &lt;code>&amp;lt;script&amp;gt;&lt;/code> 嵌入它，讓舊瀏覽器補上新的 JavaScript 功能。它的經過有完整的公開時間線：&lt;/p>
&lt;ul>
&lt;li>2024 年 2 月，網域與 GitHub 帳號轉給新的經營者（&lt;a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024&lt;/a>），Cloudflare 的文章寫明新經營者是 Funnull。Cloudflare 隨即在 cdnjs 推出鏡像，理由是「any website embedding a link to the original polyfill.io domain, will now be relying on Funnull to maintain and secure the underlying project」（&lt;a href="https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk/">Cloudflare, 2024-02&lt;/a>）；Fastly 也提供了替代網址（&lt;a href="https://community.fastly.com/t/new-options-for-polyfill-io-users/2540">Fastly 社群, 2024&lt;/a>）。&lt;/li>
&lt;li>2024 年 6 月 25 日，Sansec 公布這個網域自易手之後就對行動裝置注入惡意程式碼、把訪客導向博弈網站，受影響的網站超過十萬個（&lt;a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024&lt;/a>）。&lt;/li>
&lt;li>隔天 Cloudflare 對免費方案的網站預設把 polyfill.io 的連結改寫到自己的鏡像，並說明刻意不直接封鎖這個網域，因為「it could cause widespread web outages given how broadly polyfill.io is used with some estimates indicating usage on nearly 4% of all websites」（&lt;a href="https://blog.cloudflare.com/automatically-replacing-polyfill-io-links-with-cloudflares-mirror-for-a-safer-internet/">Cloudflare, 2024-06&lt;/a>）。&lt;/li>
&lt;li>2025 年 5 月，美國財政部制裁 Funnull，新聞稿寫它在 2024 年購入一個開發者使用的程式碼庫並惡意修改（未點名 polyfill）（&lt;a href="https://home.treasury.gov/news/press-releases/sb0149">美國財政部, 2025&lt;/a>）。&lt;/li>
&lt;/ul>
&lt;p>整個過程裡，沒有任何機制直接通知嵌入這個腳本的網站：警告只出現在原作者與 Cloudflare、Fastly 的公開文章裡，看到文章的網站才會換掉；保護其餘網站的是 CDN 業者與資安研究者的介入，網域的處置本身沒有任何安排。被大量嵌入的引用，網域的處置等於在決定所有嵌入者的安全；出售這個網域，就是把所有嵌入者交給買方；這是軟體供應鏈攻擊的一條路徑，建置與發布端的供應鏈信任見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性與 Artifact 信任&lt;/a>。嵌入者這一側的防護（內容安全政策與第三方腳本的監控）見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/cloudflare-waf/page-shield-csp-sri/" data-link-title="Cloudflare Page Shield：用 CSP &amp;#43; SRI &amp;#43; script monitoring 防 client-side supply chain" data-link-desc="Page Shield 三層防禦（CSP / SRI / script monitoring）對應 Magecart / formjacking / skimmer / 第三方 SDK 注入的不同 attack pattern、Cloudflare dashboard &amp;#43; API 配置、四個 production 踩雷（inline script 漏 / dynamic loader / CSP report 噪音 / SRI hash mismatch）、跟 dev workflow &amp;#43; WAF 整合">Cloudflare Page Shield：用 CSP + SRI + script monitoring 防 client-side supply chain&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>本篇的範圍是服務結束時網域與名稱的處置。外部引用（服務發出去、存在別人手上的網址、端點、名稱與身分）在服務結束後有兩種結局：失效是引用不再得到回應，易手是引用指向的網域或名稱換了主人、回應由新主人決定（定義見 <a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a>）；決定走向哪一種的，是引用所在的網域或名稱之後歸誰。上線時怎麼選命名空間見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a>，這篇處理結束時已經發出去、存在別人手上的那些網域與名稱。</p>
<h2 id="網域在服務結束後的去向續約並回應續約不回應到期出售">網域在服務結束後的去向：續約並回應、續約不回應、到期、出售</h2>
<p>服務停止營運之後，它的網域有下列幾種去向，差別在引用會繼續運作、失效還是易手：</p>
<ul>
<li><strong>繼續續約、維持轉址或靜態內容</strong>：引用繼續運作。成本只剩網域續約與一個能回應請求的最小服務，做法見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>。</li>
<li><strong>繼續續約、但不回應任何內容</strong>：引用失效，但不會易手。在不讓網域落到別人手上的做法裡，這是成本最低的一種。</li>
<li><strong>讓網域到期</strong>：網域釋出之後任何人都能註冊，引用可能易手。</li>
<li><strong>出售或轉讓</strong>：引用確定易手。沒有任何公告的出售，新主人可以照常提供服務，也可以不；公開移交（事先公告接手者、在條款裡寫明延續範圍，條件見 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a> 的〈交給接手的社群或公司〉）讓持有者事先知道新主人是誰，但條款本身沒有被執行的機制。</li>
</ul>
<p>讓網域到期與沒有公告的出售，對持有者的後果相同：持有者手上的引用照常運作，回應的內容卻由持有者不知道的新主人決定。所以網域的去向要在退場計畫裡明寫，而不是留給公司解散時的資產處分程序決定。短網址服務的需求定義也把網域的去向列進保存承諾：服務結束營運時，「網域在停止營運後由誰續約」是保存承諾的一部分（<a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格</a> 的〈連結的保存期限與保存承諾〉）。</p>
<p>「續約但不回應」要在網域的每一層都做到不回應。網域沒有設定內容時，有些註冊商會預設掛上停放頁並放廣告，回應的內容就變成註冊商決定的，所以要到註冊商的網域設定裡確認停放功能是關的。網域上原本收信的 MX 紀錄也要決定去向：移除它讓寄往舊信箱的信退回，或保留一個有人看管的信箱，讓重設密碼信與客戶的支援信都有人處理。指向雲端 IP 的 A／AAAA 紀錄與萬用字元紀錄也要刪除，雲端業者把那個 IP 分給別人之後，紀錄就會懸空。網域不再寄信時，可以發布表明這個網域不寄信的 SPF 與 DMARC 紀錄，讓別人冒用網域寄出的信被收件端拒收。</p>
<p>續約要持續多久，要分兩種網域判斷。曾經被當成帳號信箱使用的網域，放手的條件是綁在這個網域上的帳號全部改綁到別的信箱，這要另外盤點，和流量無關：本篇〈綁在可過期網域上的帳號身分〉一節的 <code>ctx</code> 事件，被重新註冊的就是一個閒置的網域。其他網域看還有沒有引用在被使用：網域釋出之後，註冊它的人能接收所有仍在發出的請求，所以放手的風險隨著請求量與被嵌入的程度上升。判斷請求量需要一份紀錄。掃描器與爬蟲的請求要從請求本身（User-Agent、請求的路徑）排除，這只有 HTTP 存取紀錄做得到，所以判斷要在還保有存取紀錄的靜態封存階段做（見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>）；關閉之後只剩自己名稱伺服器的查詢紀錄，它看到的是解析器而不是發出請求的人，分不出掃描器，解析器的快取又讓數字偏低，只能當趨勢參考，不能用來判定歸零。沒有這份紀錄、或請求一直沒有歸零時，續約是一筆沒有終點的固定成本，要在退場計畫裡當成長期支出編列；多數註冊商允許一次續約多年，可以在公司還有資金時先付清，並寫明之後由誰接著付。</p>
<h2 id="網域的出售polyfillio-的經過">網域的出售：polyfill.io 的經過</h2>
<p>polyfill.io 是一個公共 CDN 腳本，網站以 <code>&lt;script&gt;</code> 嵌入它，讓舊瀏覽器補上新的 JavaScript 功能。它的經過有完整的公開時間線：</p>
<ul>
<li>2024 年 2 月，網域與 GitHub 帳號轉給新的經營者（<a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024</a>），Cloudflare 的文章寫明新經營者是 Funnull。Cloudflare 隨即在 cdnjs 推出鏡像，理由是「any website embedding a link to the original polyfill.io domain, will now be relying on Funnull to maintain and secure the underlying project」（<a href="https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk/">Cloudflare, 2024-02</a>）；Fastly 也提供了替代網址（<a href="https://community.fastly.com/t/new-options-for-polyfill-io-users/2540">Fastly 社群, 2024</a>）。</li>
<li>2024 年 6 月 25 日，Sansec 公布這個網域自易手之後就對行動裝置注入惡意程式碼、把訪客導向博弈網站，受影響的網站超過十萬個（<a href="https://sansec.io/research/polyfill-supply-chain-attack">Sansec, 2024</a>）。</li>
<li>隔天 Cloudflare 對免費方案的網站預設把 polyfill.io 的連結改寫到自己的鏡像，並說明刻意不直接封鎖這個網域，因為「it could cause widespread web outages given how broadly polyfill.io is used with some estimates indicating usage on nearly 4% of all websites」（<a href="https://blog.cloudflare.com/automatically-replacing-polyfill-io-links-with-cloudflares-mirror-for-a-safer-internet/">Cloudflare, 2024-06</a>）。</li>
<li>2025 年 5 月，美國財政部制裁 Funnull，新聞稿寫它在 2024 年購入一個開發者使用的程式碼庫並惡意修改（未點名 polyfill）（<a href="https://home.treasury.gov/news/press-releases/sb0149">美國財政部, 2025</a>）。</li>
</ul>
<p>整個過程裡，沒有任何機制直接通知嵌入這個腳本的網站：警告只出現在原作者與 Cloudflare、Fastly 的公開文章裡，看到文章的網站才會換掉；保護其餘網站的是 CDN 業者與資安研究者的介入，網域的處置本身沒有任何安排。被大量嵌入的引用，網域的處置等於在決定所有嵌入者的安全；出售這個網域，就是把所有嵌入者交給買方；這是軟體供應鏈攻擊的一條路徑，建置與發布端的供應鏈信任見 <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性與 Artifact 信任</a>。嵌入者這一側的防護（內容安全政策與第三方腳本的監控）見 <a href="/blog/backend/07-security-data-protection/vendors/cloudflare-waf/page-shield-csp-sri/" data-link-title="Cloudflare Page Shield：用 CSP &#43; SRI &#43; script monitoring 防 client-side supply chain" data-link-desc="Page Shield 三層防禦（CSP / SRI / script monitoring）對應 Magecart / formjacking / skimmer / 第三方 SDK 注入的不同 attack pattern、Cloudflare dashboard &#43; API 配置、四個 production 踩雷（inline script 漏 / dynamic loader / CSP report 噪音 / SRI hash mismatch）、跟 dev workflow &#43; WAF 整合">Cloudflare Page Shield：用 CSP + SRI + script monitoring 防 client-side supply chain</a>。</p>
<h2 id="交給中立方的技術控制">交給中立方的技術控制</h2>
<p>讓網域繼續運作、又不由自己維運的做法，是把網域的<strong>技術控制</strong>交給一個中立的一方：由對方設定 DNS 與回應內容，所有權仍留在原處。2009 年由短網址業者組成、Internet Archive 管理的封存計畫 301Works，參與條款就是這樣設計的：參與的短網址業者關站時，「technical control of the shortening service domain will be transferred to 301Works.org in order to continue redirecting existing shortened URLs」，並註明「This does not mean that the company will transfer ownership of the domain in such a case」（<a href="https://web.archive.org/web/20091114063044/http://www.301works.org/post/240736199/url-shorteners-working-with-internet-archive-for">Internet Archive, 2009</a>；<a href="https://web.archive.org/web/20091116124902/http://www.301works.org/post/220152694/terms-of-participation">301Works 參與條款</a>）。</p>
<p>技術控制的移交要在服務還活著時簽好，結束時才去找，通常已經來不及。tr.im 在 2009 年宣布關站、隨即撤回並改說要開源交給社群時，承諾把網域捐給一個代為持有的第三方：「donate the tr.im domain name to a 3rd party that will hold it in trust」（<a href="https://web.archive.org/web/20090922172221/http://blog.tr.im/post/189122283/open-source-release">tr.im, 2009</a>），但可查到的網頁快照顯示，2012 年網域只剩空白的目錄列表，2013 年起由新的經營者重新上線，沒有找到網域交給代持第三方的公告。承諾本身沒有被執行的機制。條款裡要寫明的還有續約費由誰付，以及網域所有權轉手時（例如公司解散、資產出售）技術控制是否延續。</p>
<h2 id="名稱的保留與回收套件名稱雲端資源名稱與短碼">名稱的保留與回收：套件名稱、雲端資源名稱與短碼</h2>
<p>共用登錄服務裡的名稱（套件名稱、雲端資源名稱、使用者名稱）被刪除之後能不能被別人重新申請，決定了引用這個名稱的人會不會在不知情的情況下拿到別人發布的內容。</p>
<p>npm 的 left-pad 事件展示了名稱被回收時發生的事。2016 年 3 月 22 日，一位作者下架了自己的 273 個套件，其中 left-pad 被大量專案間接依賴，建置隨即失敗。有人在十分鐘內以同一個名稱重新發布了功能相同的套件，npm 說明當時的規則：「we allow anyone to use an abandoned package name as long as they don&rsquo;t use the same version numbers」；被下架的其他名稱則由社群搶先發布佔位套件，「to prevent malicious publishing of modules under their names」（<a href="https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm">npm, 2016</a>）。一週後 npm 修改政策：全部版本被移除的套件，以安全佔位套件取代，讓名稱不會被惡意搶註（<a href="https://blog.npmjs.org/post/141905368000/changes-to-npms-unpublish-policy">npm, 2016</a>）；現行政策延續了同一個名稱的同一個版本號不能再使用的規定，並把下架限縮在發布 72 小時內且沒有其他套件相依，或沒有其他套件相依、上週下載少於 300 次、只有單一維護者的套件，其餘只能標記為棄用；全部版本下架之後，文件只寫 24 小時內不能再發布新版本，沒有寫名稱會不會被佔位（<a href="https://docs.npmjs.com/policies/unpublish">npm unpublish 政策</a>）。版本號不重用保護的是 lockfile：lockfile 記下確切版本與內容雜湊，在重新產生 lockfile 之前，安裝拿到的不會是別人發布的內容；重新解析版本範圍時，才可能拿到新主人發布的新版本。</p>
<p>雲端服務對資源名稱有類似的保留：Azure 的經典雲端服務（classic cloud service，<code>*.cloudapp.net</code>）刪除後，對應的 DNS 名稱在保留期內只允許原本租用戶的訂閱重新使用，保留期過後任何訂閱都能申請（<a href="https://learn.microsoft.com/en-us/azure/security/fundamentals/subdomain-takeover">Microsoft Learn</a>）；App Service 刪除後同樣只讓原本的租用戶重新使用這個名稱，保留多久文件沒有寫明（<a href="https://learn.microsoft.com/en-us/azure/app-service/reference-dangling-subdomain-prevention">Microsoft Learn</a>）。短網址服務的短碼也是發出去之後會被引用、刪除後可能被重新申請的名稱，<a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格</a> 寫過回收的短碼不能再發給新的連結，要留一筆紀錄佔住；這筆紀錄不設清除期限，否則名稱到期後又回到可申請的池子。</p>
<p>npm 2016 年的規則與短碼的佔位紀錄，共同做法是<strong>佔位</strong>：名稱不再使用時，留一筆沒有期限的紀錄讓它不能被重新申請，而不是把它放回可申請的池子。npm 的佔位名稱仍可由其他人向 npm 申請、經人工審核後轉給對方。Azure 的保留期是對照：它讓原租用戶有時間清理，期限一到名稱就回到可申請的池子。佔位的成本是一筆紀錄，放回池子的代價，是所有還持有這個名稱的引用都可能易手。佔位紀錄只需要名稱、狀態與刪除時間，不需要保留原擁有者的個人資料，所以它和刪除個人資料的要求可以並存。</p>
<h2 id="懸空-dns-與子網域接管">懸空 DNS 與子網域接管</h2>
<p><strong>懸空 DNS</strong>（dangling DNS）指一筆 DNS 紀錄還指向一個已經刪除的雲端資源。客戶在自己的網域設定一筆 CNAME，指向雲端服務給的主機名稱；資源刪除之後，這筆紀錄如果沒有一起刪除，而那個主機名稱又被別人重新申請，客戶的子網域就由別人提供內容，稱為<strong>子網域接管</strong>（subdomain takeover）。Microsoft 的文件寫明接管者能做的事不只放一個假網頁：「a threat actor can use the hijacked subdomain to apply for and receive a valid SSL certificate」（<a href="https://learn.microsoft.com/en-us/azure/security/fundamentals/subdomain-takeover">Microsoft Learn</a>）。</p>
<p>同一種成因也出現在 A 紀錄上：紀錄指向雲端主機的公開 IP，主機終止（有些業者是停止）時業者收回這個 IP、再分配給別的客戶，拿到它的人就收得到這個網域的流量，也能用 HTTP 驗證替它申請憑證；哪些暫停與重建的做法會把 IP 還給業者，見 <a href="/blog/devops/operations/09-host-lifecycle/suspend-and-resume-service/" data-link-title="服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序" data-link-desc="一台主機上的服務要暫停一段時間再恢復：只停服務、停止主機、終止主機保留設定、全部刪除靠 repo 重建各自保留什麼與付出什麼，怎麼依保留的月費、重建的成本與漂移量選擇，暫停期間仍在計費的資源（含在作業系統裡 shutdown 不停止計費），仍被 DNS 與 CDN 指向的結果（Cloudflare 521、522，釋放 IP 之後的懸空 DNS），恢復前要重新確認的漂移，以及依元件依賴排出的恢復順序與驗證">服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序</a>〈暫停期間仍指向這台主機的外部設定〉。客戶這一側的防護是刪除雲端資源（或釋放 IP）之前先刪掉指向它的 DNS 紀錄，並定期拿自家網域裡指向雲端主機名的 CNAME 與指向雲端 IP 的 A 紀錄對照仍存在的資源；本節展開的是提供者這一側。防止子網域接管的工作橫跨客戶與提供者兩方：DNS 紀錄在客戶的網域裡，提供者無法替客戶刪除；主機名稱在提供者的命名空間裡，能不能被別人重新申請由提供者決定。提供者這一側能做的有下列幾件：</p>
<ul>
<li><strong>刪除後保留名稱一段時間</strong>，只給原本的租用戶重新使用（〈名稱的保留與回收：套件名稱、雲端資源名稱與短碼〉一節的 Azure 規則）。</li>
<li><strong>要求驗證網域的所有權</strong>：GitHub Pages 讓帳號驗證自己的網域，驗證之後只有這個帳號能把網站發布到這個網域，並列出接管發生的情形：「when you delete your repository, when your billing plan is downgraded, or after any other change which unlinks the custom domain or disables GitHub Pages while the domain remains configured for GitHub Pages and is not verified」（<a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/verifying-your-custom-domain-for-github-pages">GitHub Docs</a>）。</li>
<li><strong>讓預設主機名稱無法被預測</strong>：見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 的〈外部引用所屬的命名空間：提供者網域、客戶網域與共用登錄服務〉裡 Azure 的雜湊主機名稱。</li>
</ul>
<p>服務本身結束時，保留名稱、驗證網域與不可預測的主機名稱這幾項做法要延續下去：提供者自己的網域裡，所有曾經發給客戶的子網域（例如 <code>&lt;app&gt;.herokuapp.com</code> 這一類）都不能放回可申請的池子。Heroku 在 2022 年終止免費方案時，把免費的 app 縮到零個執行個體，而不是刪除 app：程式、設定與名稱都保留，只是不再有執行中的程序（<a href="http://web.archive.org/web/20220825195919id_/https://help.heroku.com/RSBRUH58/removal-of-heroku-free-product-plans-faq">Heroku FAQ，Wayback 2022-08-25</a>）。同一次處置從 2022 年 10 月起刪除閒置一年以上的帳號（<a href="https://www.heroku.com/blog/next-chapter/">Heroku, 2022</a>）；刪除帳號這一條路，正是曾經發給客戶的子網域名稱可能回到可申請池子的地方。</p>
<h2 id="綁在可過期網域上的帳號身分">綁在可過期網域上的帳號身分</h2>
<p><a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a> 列的外部引用裡，綁在網域上的身分是最不顯眼的一種：套件登錄服務用維護者的信箱寄送重設密碼的信，而信箱屬於維護者自己的網域。網域過期、被別人註冊之後，新主人能收到重設密碼的信，接管維護者的帳號與帳號下的所有套件。</p>
<p>2022 年 5 月 14 日，PyPI 套件 <code>ctx</code> 擁有者帳號的信箱網域被重新註冊，事件報告記錄網域註冊後十二分鐘，密碼重設與惡意版本的上傳就開始了（<a href="https://python-security.readthedocs.io/pypi-vuln/index-2022-05-24-ctx-domain-takeover.html">PyPI 事件報告</a>）。同一時期的研究在 npm 找到 2,818 個維護者信箱屬於已過期的網域，可以藉此接管 8,494 個套件（<a href="https://arxiv.org/abs/2112.10165">Zahan 等, 2022</a>）。PyPI 自 2025 年起定期檢查信箱網域的狀態，網域進入過期階段時取消該信箱的驗證（PyPI 稱這類事件為 domain resurrection），到 2025 年 8 月已取消 1,800 個以上（<a href="https://blog.pypi.org/posts/2025-08-18-preventing-domain-resurrections/">PyPI, 2025</a>）。</p>
<p>綁在網域上的帳號身分對服務終止有兩層意義。第一層在提供者自己：提供者的網域到期、被別人註冊之後，新主人收得到寄往這個網域的所有信，包括提供者自己在其他服務上的帳號的重設密碼信，因而能接管那些帳號，也收得到客戶寄來的支援信。行銷追蹤後台是這一層常見的例子：Meta 商家資產管理組合與 GTM、GA4 帳戶的成員是個人帳號，用公司網域信箱註冊的 Facebook 帳號、或用公司網域信箱建立的 Google 帳號（不由 Google Workspace 管理的那一種），重設密碼信都寄往這個網域，帳號沒有另外綁定兩步驟驗證或其他復原方式時，取得網域的新主人藉重設密碼就能接管這些帳號，連同它們在組合與帳戶裡的管理權（由重設密碼的機制推出）；成員的組成與移交方式見<a href="/blog/devops/monitoring/vendors/tracking-account-ownership/" data-link-title="追蹤資產的歸屬：Meta、GTM 與 GA4 的擁有層級、權限分級與移交方式" data-link-desc="Meta Pixel 的資料集、GTM 容器與 GA4 資源（property）各自屬於哪一層的擁有者，三家的權限怎麼分級，哪些資產能換到另一個擁有者底下，以及公司自營、委託代理商、換代理商與事業轉手時的歸屬安排">追蹤資產的歸屬</a>。第二層在登錄服務：它在這一種引用裡是持有者，同時經營自己的帳號系統，要把「維護者信箱的網域是否仍有效」當成帳號安全的一部分持續檢查，而不是只在註冊時驗證一次。</p>
]]></content:encoded></item><item><title>10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</title><link>https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/</guid><description>&lt;p>服務結束（英文常稱 sunset 或 decommissioning）時，從「完整運作」到「完全關閉」之間可以安排幾個階段；本篇整理這些階段，以及依使用紀錄決定保留範圍、推動遷移的手段、公告期與補償。持有者的三類（直接客戶、間接使用者、第三方持有者）見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者&lt;/a>；服務仍在、只淘汰一個 API 版本時的階段安排見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation&lt;/a>。&lt;/p>
&lt;h2 id="退場階段停止新建唯讀靜態封存與關閉">退場階段：停止新建、唯讀、靜態封存與關閉&lt;/h2>
&lt;p>Google 在 2015 年 3 月宣布關閉程式碼託管服務 Google Code 時，公告列出三個日期（&lt;a href="https://opensource.googleblog.com/2015/03/farewell-to-google-code.html">Google Open Source Blog, 2015&lt;/a>）：當天停止建立新專案；2015 年 8 月 24 日起網站變成唯讀，「You can still checkout/view project source, issues, and wikis」；2016 年 1 月 25 日關閉託管服務，改提供原始碼、issue 與 wiki 的封存檔下載，承諾提供到 2016 年底。Microsoft 在 2017 年 3 月宣布關閉 CodePlex 時走了同樣的順序：當天停止建立新專案，10 月唯讀，12 月 15 日改成唯讀的輕量封存（&lt;a href="https://devblogs.microsoft.com/bharry/shutting-down-codeplex/">Microsoft, 2017&lt;/a>）。&lt;/p>
&lt;p>這兩個案例共用的順序可以一般化成下列階段，每一個階段比前一個保留的少、成本也低，也讓還能自己行動的持有者在下一階段開始前先離開：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>保留什麼&lt;/th>
 &lt;th>停掉什麼&lt;/th>
 &lt;th>對持有者的效果&lt;/th>
 &lt;th>維運成本&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>停止新建&lt;/td>
 &lt;td>既有的一切照常運作&lt;/td>
 &lt;td>新帳號、新資源、新引用&lt;/td>
 &lt;td>讓外部引用的數量不再增加&lt;/td>
 &lt;td>和正常營運相近&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>唯讀（read-only）&lt;/td>
 &lt;td>既有內容可讀取、可匯出，外部引用繼續回應&lt;/td>
 &lt;td>所有寫入&lt;/td>
 &lt;td>直接客戶有時間匯出；第三方持有者的引用照常運作&lt;/td>
 &lt;td>降低：不必處理寫入；支援範圍縮到匯出相關，既有內容的濫用回報仍要處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>靜態封存&lt;/td>
 &lt;td>既有內容轉成靜態檔，外部引用由靜態檔或轉址規則回應&lt;/td>
 &lt;td>應用程式、資料庫、帳號系統&lt;/td>
 &lt;td>第三方持有者的引用改由靜態檔或轉址回應&lt;/td>
 &lt;td>網域與 TLS 憑證的續期、靜態檔的儲存與流量，加上下架單一引用的管道&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>關閉&lt;/td>
 &lt;td>只剩網域本身，續約但不回應&lt;/td>
 &lt;td>全部&lt;/td>
 &lt;td>引用失效，但不會易手&lt;/td>
 &lt;td>只剩網域續約&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>靜態封存保得住的是只需讀取、不需認證、回應不隨請求內容變化的引用：網址的轉址、被嵌入的固定版本腳本、套件的檔案都屬於這一類；需要寫入或認證的端點，以及依請求動態產生回應的服務，到這一階段就失效，要延續它們得靠 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構&lt;/a> 的接手。&lt;/p>
&lt;p>關閉之後若不再續約或出售網域，引用會從失效變成易手（網域換了主人，引用回應的是新主人的內容），各種去向的比較見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a> 的〈網域在服務結束後的去向：續約並回應、續約不回應、到期、出售〉。&lt;/p>
&lt;p>停止新建這一階段本身省不下成本，但它是後面每一階段的前提：外部引用還在增加時，任何保留範圍的估算都會過時。API 版本淘汰也是先停止新的使用、再處理既有使用者，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation&lt;/a> 的〈Deprecation 的執行工具箱〉。&lt;/p>
&lt;p>&lt;strong>靜態封存&lt;/strong>是讓第三方持有者的引用繼續運作的最低成本做法。短網址服務把對照表匯出成靜態的轉址規則，放在物件儲存或 CDN 上（做法見 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/url-shortener-implementation/" data-link-title="0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄" data-link-desc="以需求已定案為前提的實作取捨：建立與轉址兩條路徑的流量形狀、各種短碼生成方式的可猜測性與協調成本、轉址狀態碼與各層快取怎麼影響停用或修改連結多久生效、點擊事件的非同步紀錄、濫用防護、服務結束時的靜態轉址與網域去向，以及換掉任一項需求決定時要重選的實作位置">0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄&lt;/a> 的〈服務結束時的轉址延續〉）；程式碼託管服務把每個專案頁面輸出成靜態 HTML。CodePlex 的公告另外寫了兩件為持有舊網址的人做的事：「Where possible, we&amp;rsquo;ll put in place redirects so that existing URLs work, or at least redirect you to the project&amp;rsquo;s new homepage on the archive」，以及封存會沿用專案擁有者設定的「I&amp;rsquo;ve moved」指向，把訪客帶到專案的新家（&lt;a href="https://devblogs.microsoft.com/bharry/shutting-down-codeplex/">Microsoft, 2017&lt;/a>）。轉址與沿用「I&amp;rsquo;ve moved」指向，是 CodePlex 為第三方持有者做的安排：他們手上只有舊網址，轉址讓那些網址繼續有用。靜態封存期間仍要保留下架單一引用的管道：已經轉址到惡意目的地的連結，以及目的地網域到期後被別人註冊的連結，在封存之後照樣把人送過去，要能單筆改寫成說明頁或回 404。&lt;/p>
&lt;p>退到靜態檔之後，應用程式的紀錄跟著消失，還量得到的是 CDN 或物件儲存的存取紀錄；關閉之後只剩自己名稱伺服器的查詢紀錄。封存後還要判斷保留範圍的話，這些紀錄要在封存前先開啟。單筆下架能不能到達已經點過連結的瀏覽器，取決於轉址回應帶的快取標頭，見 &lt;a href="https://tarrragon.github.io/blog/backend/12-http-caching/applying-cache-headers/" data-link-title="12.8 快取設定的應用：轉址、需登入的 API 回應與個人化頁面" data-link-desc="把 HTTP 快取的保存位置、新鮮度、驗證、快取鍵與更新規則用在具體回應上：永久與暫時轉址的狀態碼與快取標頭、需登入的 API 回應、個人化頁面、靜態資源與引用它的頁面，以及檢查實際送出的快取標頭的方法">12.8 快取設定的應用：轉址、需登入的 API 回應與個人化頁面&lt;/a>。&lt;/p>
&lt;p>靜態封存的成本低，但不是零：網域要續約、有人要決定它保留多久。到 2026 年 10 月，Google Code 的封存頁面仍可存取，遠超過承諾的 2016 年底；CodePlex 的公告寫過「There isn&amp;rsquo;t currently any plan to have an end date for the archive」，封存網址現在卻已經轉到 Microsoft 的首頁，Microsoft 何時撤掉封存網站，沒有公告。到了靜態封存與關閉階段，團隊多半已經解散，而網域、TLS 憑證、雲端帳單與註冊商帳號都還有到期日；每一項都要在解散前寫明保管人與付款來源，否則它們會像 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a> 裡那些過期的信箱網域一樣，在沒人注意時到期。&lt;/p></description><content:encoded><![CDATA[<p>服務結束（英文常稱 sunset 或 decommissioning）時，從「完整運作」到「完全關閉」之間可以安排幾個階段；本篇整理這些階段，以及依使用紀錄決定保留範圍、推動遷移的手段、公告期與補償。持有者的三類（直接客戶、間接使用者、第三方持有者）見 <a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a>；服務仍在、只淘汰一個 API 版本時的階段安排見 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a>。</p>
<h2 id="退場階段停止新建唯讀靜態封存與關閉">退場階段：停止新建、唯讀、靜態封存與關閉</h2>
<p>Google 在 2015 年 3 月宣布關閉程式碼託管服務 Google Code 時，公告列出三個日期（<a href="https://opensource.googleblog.com/2015/03/farewell-to-google-code.html">Google Open Source Blog, 2015</a>）：當天停止建立新專案；2015 年 8 月 24 日起網站變成唯讀，「You can still checkout/view project source, issues, and wikis」；2016 年 1 月 25 日關閉託管服務，改提供原始碼、issue 與 wiki 的封存檔下載，承諾提供到 2016 年底。Microsoft 在 2017 年 3 月宣布關閉 CodePlex 時走了同樣的順序：當天停止建立新專案，10 月唯讀，12 月 15 日改成唯讀的輕量封存（<a href="https://devblogs.microsoft.com/bharry/shutting-down-codeplex/">Microsoft, 2017</a>）。</p>
<p>這兩個案例共用的順序可以一般化成下列階段，每一個階段比前一個保留的少、成本也低，也讓還能自己行動的持有者在下一階段開始前先離開：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>保留什麼</th>
          <th>停掉什麼</th>
          <th>對持有者的效果</th>
          <th>維運成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>停止新建</td>
          <td>既有的一切照常運作</td>
          <td>新帳號、新資源、新引用</td>
          <td>讓外部引用的數量不再增加</td>
          <td>和正常營運相近</td>
      </tr>
      <tr>
          <td>唯讀（read-only）</td>
          <td>既有內容可讀取、可匯出，外部引用繼續回應</td>
          <td>所有寫入</td>
          <td>直接客戶有時間匯出；第三方持有者的引用照常運作</td>
          <td>降低：不必處理寫入；支援範圍縮到匯出相關，既有內容的濫用回報仍要處理</td>
      </tr>
      <tr>
          <td>靜態封存</td>
          <td>既有內容轉成靜態檔，外部引用由靜態檔或轉址規則回應</td>
          <td>應用程式、資料庫、帳號系統</td>
          <td>第三方持有者的引用改由靜態檔或轉址回應</td>
          <td>網域與 TLS 憑證的續期、靜態檔的儲存與流量，加上下架單一引用的管道</td>
      </tr>
      <tr>
          <td>關閉</td>
          <td>只剩網域本身，續約但不回應</td>
          <td>全部</td>
          <td>引用失效，但不會易手</td>
          <td>只剩網域續約</td>
      </tr>
  </tbody>
</table>
<p>靜態封存保得住的是只需讀取、不需認證、回應不隨請求內容變化的引用：網址的轉址、被嵌入的固定版本腳本、套件的檔案都屬於這一類；需要寫入或認證的端點，以及依請求動態產生回應的服務，到這一階段就失效，要延續它們得靠 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a> 的接手。</p>
<p>關閉之後若不再續約或出售網域，引用會從失效變成易手（網域換了主人，引用回應的是新主人的內容），各種去向的比較見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈網域在服務結束後的去向：續約並回應、續約不回應、到期、出售〉。</p>
<p>停止新建這一階段本身省不下成本，但它是後面每一階段的前提：外部引用還在增加時，任何保留範圍的估算都會過時。API 版本淘汰也是先停止新的使用、再處理既有使用者，見 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a> 的〈Deprecation 的執行工具箱〉。</p>
<p><strong>靜態封存</strong>是讓第三方持有者的引用繼續運作的最低成本做法。短網址服務把對照表匯出成靜態的轉址規則，放在物件儲存或 CDN 上（做法見 <a href="/blog/backend/00-service-selection/url-shortener-implementation/" data-link-title="0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄" data-link-desc="以需求已定案為前提的實作取捨：建立與轉址兩條路徑的流量形狀、各種短碼生成方式的可猜測性與協調成本、轉址狀態碼與各層快取怎麼影響停用或修改連結多久生效、點擊事件的非同步紀錄、濫用防護、服務結束時的靜態轉址與網域去向，以及換掉任一項需求決定時要重選的實作位置">0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄</a> 的〈服務結束時的轉址延續〉）；程式碼託管服務把每個專案頁面輸出成靜態 HTML。CodePlex 的公告另外寫了兩件為持有舊網址的人做的事：「Where possible, we&rsquo;ll put in place redirects so that existing URLs work, or at least redirect you to the project&rsquo;s new homepage on the archive」，以及封存會沿用專案擁有者設定的「I&rsquo;ve moved」指向，把訪客帶到專案的新家（<a href="https://devblogs.microsoft.com/bharry/shutting-down-codeplex/">Microsoft, 2017</a>）。轉址與沿用「I&rsquo;ve moved」指向，是 CodePlex 為第三方持有者做的安排：他們手上只有舊網址，轉址讓那些網址繼續有用。靜態封存期間仍要保留下架單一引用的管道：已經轉址到惡意目的地的連結，以及目的地網域到期後被別人註冊的連結，在封存之後照樣把人送過去，要能單筆改寫成說明頁或回 404。</p>
<p>退到靜態檔之後，應用程式的紀錄跟著消失，還量得到的是 CDN 或物件儲存的存取紀錄；關閉之後只剩自己名稱伺服器的查詢紀錄。封存後還要判斷保留範圍的話，這些紀錄要在封存前先開啟。單筆下架能不能到達已經點過連結的瀏覽器，取決於轉址回應帶的快取標頭，見 <a href="/blog/backend/12-http-caching/applying-cache-headers/" data-link-title="12.8 快取設定的應用：轉址、需登入的 API 回應與個人化頁面" data-link-desc="把 HTTP 快取的保存位置、新鮮度、驗證、快取鍵與更新規則用在具體回應上：永久與暫時轉址的狀態碼與快取標頭、需登入的 API 回應、個人化頁面、靜態資源與引用它的頁面，以及檢查實際送出的快取標頭的方法">12.8 快取設定的應用：轉址、需登入的 API 回應與個人化頁面</a>。</p>
<p>靜態封存的成本低，但不是零：網域要續約、有人要決定它保留多久。到 2026 年 10 月，Google Code 的封存頁面仍可存取，遠超過承諾的 2016 年底；CodePlex 的公告寫過「There isn&rsquo;t currently any plan to have an end date for the archive」，封存網址現在卻已經轉到 Microsoft 的首頁，Microsoft 何時撤掉封存網站，沒有公告。到了靜態封存與關閉階段，團隊多半已經解散，而網域、TLS 憑證、雲端帳單與註冊商帳號都還有到期日；每一項都要在解散前寫明保管人與付款來源，否則它們會像 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 裡那些過期的信箱網域一樣，在沒人注意時到期。</p>
<h2 id="依使用紀錄決定保留範圍">依使用紀錄決定保留範圍</h2>
<p>不是每一條外部引用都要保留到同一個階段（例如全部保留到靜態封存）。有使用紀錄的服務，可以依「最近還有沒有人用」把引用分開處理。</p>
<p>goo.gl 的三次公告是這個做法的完整過程。2018 年 3 月 Google 宣布分階段停止建立新的短網址：2018 年 4 月起新使用者不能建立，2019 年 3 月起全面停止，並承諾既有連結繼續轉址（<a href="https://web.archive.org/web/2018040100000/https://developers.googleblog.com/2018/03/transitioning-google-url-shortener.html">Google, 2018</a>）；2024 年 7 月宣布 2025 年 8 月 25 日起所有連結回 404，理由是超過 99% 的連結在宣布前一個月沒有活動，並從 2024 年 8 月起對部分連結先顯示<strong>中間頁</strong>（點擊短網址時先出現、告知連結即將停止運作的一頁；Google 也寫明它會打斷後續的轉址與社群平台的連結預覽，並提供一個參數讓人跳過；由程式送出的請求收到的是這一頁的 HTML 而不是轉址，告知因此到不了任何人）（<a href="https://developers.googleblog.com/en/google-url-shortener-links-will-no-longer-be-available/">Google, 2024</a>）；2025 年 8 月 Google 修改了 2024 年的計畫，改為只停用「2024 年底沒有活動」的連結，其餘保留，這一批沒有活動的連結在九個月前已經改導到告知即將停用的訊息頁；修改的理由是「these links are embedded in countless documents, videos, posts and more」（<a href="https://blog.google/technology/developers/googl-link-shortening-update/">Google, 2025</a>）。</p>
<p>這個過程裡有兩個做法可以直接沿用。一是用中間頁把通知送到第三方持有者面前：他們不在任何聯絡名單上，但只要還在用這條引用，就一定會經過它被使用的那一刻。二是依使用紀錄切出要保留的部分，讓保留的成本隨著還在用的引用數量走，而不是隨著曾經發出去的總數走。</p>
<h2 id="推動直接客戶遷移的手段">推動直接客戶遷移的手段</h2>
<p>直接客戶有能力自己遷移，而遷移的時程由他們自己排。提供者推動時程的手段有下列幾種，代價分別落在客戶的使用者、提供者自己與晚到的客戶身上：</p>
<ul>
<li><strong>分段截止日加上降級</strong>：Parse 的遷移指南把遷移拆成三個建議日期（先搬資料、再架自己的伺服器、最後發布指向新伺服器的 app 版本），並說明資料還沒在 2016 年 4 月 28 日前搬走的 app，「a small number of database requests may fail with a 428 error code」；Parse 會優先處理已經搬完的 app 的請求（<a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12</a>）。用真實的錯誤讓還沒動的客戶在自己的監控上看到問題，而不只是收到一封可能沒人讀的信（API 淘汰常用的 brownout 是同一種做法，見 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a> 的〈Deprecation 的執行工具箱〉），代價是這些客戶的使用者先承受錯誤。</li>
<li><strong>縮減而不刪除</strong>：Heroku 在 2022 年終止免費方案時，免費的 app 被縮到零個執行個體，也就是不再有執行中的程序，app 的程式、設定與名稱保留（<a href="http://web.archive.org/web/20220825195919id_/https://help.heroku.com/RSBRUH58/removal-of-heroku-free-product-plans-faq">Heroku FAQ，Wayback 2022-08-25</a>）；現行的常見問答寫明，訂閱付費方案之後可以再擴回來（<a href="https://help.heroku.com/RSBRUH58/removal-of-heroku-free-product-plans-faq">Heroku FAQ</a>）。客戶的設定與名稱都還在，回來的成本低。</li>
<li><strong>直接刪除</strong>：Heroku 同一次處置刪除了免費方案的資料庫；Parse 在 2017 年 1 月 30 日逐一停用 app 之後，「you will not be able to access the data browser or export any data」（<a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/a-parse-shutdown-reminder/">Parse, 2017</a>）。兩者都沒有唯讀期，沒趕上截止日的客戶連匯出都做不到。</li>
</ul>
<p>刪除之前留一段唯讀期，是成本最低的保險：寫入已經停了，只多付儲存與一個讀取介面的成本，換來晚到的客戶還能取回資料。</p>
<h2 id="公告期的長度與通知管道">公告期的長度與通知管道</h2>
<p>公告期是從宣布到關閉的時間，它要長到讓持有者完成他們要做的工作。下列案例的公告期從零到一年都有：Parse 約一年、Google Code 約十個月、CodePlex 約八個半月、Spotify Car Thing 約六個半月、Heroku 約三個月、Revolv 約十一週，Insteon 則是 2022 年 4 月伺服器停止之後才在官網貼出說明。</p>
<p>決定長度的是持有者要做的工作裡最慢的那一項，而不是提供者想多快結束：</p>
<ul>
<li>直接客戶要搬資料、架設替代服務，時間以月計：Parse 的遷移指南從宣布起算，給搬資料約三個月、架好自己的伺服器約六個月。</li>
<li>間接使用者要等客戶發新版 app、自己更新，時間以月計。Parse 的遷移指南把「發布指向新伺服器的 app 版本」排在最後一步，建議日期是 2016 年 9 月 28 日，離 2017 年 1 月 30 日 Parse 停用 app 還有四個月，留給使用者更新。提供者看得到請求裡的 SDK 或 app 版本，舊版客戶端的流量比例降到多少，是決定公告期與降級時點的直接依據；請求要帶哪些識別才量得到，見 <a href="/blog/backend/11-api-design/consumer-usage-observability/" data-link-title="11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界" data-link-desc="對外契約的變更決定各自需要的用量證據：觀測要建到什麼粒度、消費者身分怎麼認、哪些維度要全量常設、哪些可以抽樣或開取樣窗口，以及固定形狀 REST 拿不到欄位級用量的替代做法">11.12 API 消費者用量觀測：契約決策要的觀測維度、消費者身分的識別、欄位級用量與全量或抽樣的成本邊界</a>。</li>
<li>第三方持有者收不到公告信與公告文章；送得到他們面前的是引用被使用那一刻的訊息，依引用種類不同：網址用中間頁，套件用登錄服務的棄用警告（npm 的 deprecate 會在每次下載時顯示作者寫的訊息，見 <a href="https://docs.npmjs.com/policies/unpublish">npm unpublish 政策</a>），API 用回應標頭。這些訊息顯示多久，就是他們實際得到的公告期；這段期間沒有使用引用的人，得到的公告期是零。除此之外，保護他們的是靜態封存、依使用紀錄保留，以及 CDN 業者這類中介者的介入（<a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈網域的出售：polyfill.io 的經過〉）。</li>
</ul>
<p>這條判斷標準假設提供者撐得到那一天。資金用完、公司解散或合約終止時，提供者能給的時間可能短於最慢的那一項工作，Insteon 的公告期就是零；有些工作根本沒有完成的一天，例如沒有更新管道的裝置、印在紙上的網址。這段差距只能由不靠提供者存續的做法承接：靜態封存、預先付清的網域續約、交給中立方的技術控制，以及 <a href="/blog/backend/10-system-evolution/handover-and-archival/" data-link-title="10.8 移交：資料匯出、開源與交給社群或封存機構" data-link-desc="服務結束時把讓外部引用繼續運作所需的資料、程式碼與網域交出去：交給直接客戶的資料匯出、開源程式碼能不能獨立運作、社群接手需要提供者事先留下什麼、封存機構的兩種取得方式（業者合作交出與外部爬取），以及交不出去的部分">10.8 移交：資料匯出、開源與交給社群或封存機構</a> 的移交。這些安排的費用與負責人，要在資金還夠的時候先定下來。</p>
<p>通知管道要依持有者的類別分開設計：直接客戶用信件與管理介面；間接使用者要靠直接客戶轉達，提供者能做的是給直接客戶可以轉達的材料；第三方持有者只能在引用被使用的那一刻通知，也就是中間頁或回應裡的訊息。API 的使用者還可以從回應標頭機器可讀地得知退場時點，做法見 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a> 的 Sunset header；各類通知工具怎麼組合覆蓋不同類型的消費者，見 <a href="/blog/backend/knowledge-cards/deprecation-lifecycle/" data-link-title="Deprecation Lifecycle（棄用生命週期）" data-link-desc="從宣告棄用到全面停用之間、用哪些工具組合把通訊觸及率覆蓋到不同類型的消費者">Deprecation Lifecycle</a> 卡；那張卡依「消費者會看到哪一種通知」分類，和這裡依「提供者聯絡得到誰」分的持有者類別是兩種切法。</p>
<h2 id="賣出去的裝置的補償">賣出去的裝置的補償</h2>
<p>服務結束讓已經賣出去的硬體失效時，公告期之外還有補償的問題。兩個案例的時序相似：Revolv 的關閉公告最早出現在 2016 年 2 月下旬的網頁快照裡，客服說明只寫「Please contact Revolv customer support」，全額退款的文字到 4 月上旬的快照才出現（<a href="http://web.archive.org/web/20160405215954id_/http://revolv.com/">Revolv 官網，Wayback 2016-04-05</a>；<a href="http://web.archive.org/web/20160411184606id_/http://revolv.com/">Wayback 2016-04-11</a>）。Spotify 在 2024 年 5 月 23 日公布 Car Thing 停止支援時，支援頁沒有退款條目，「憑購買證明聯絡客服討論退款」的文字出現在 5 月 28 日到 30 日之間的快照，5 月 30 日 TechCrunch 報導了一件針對 Car Thing 的集體訴訟（<a href="http://web.archive.org/web/20240528202400id_/https://support.spotify.com/us/article/car-thing-discontinued/">Spotify 支援頁，Wayback 2024-05-28</a>；<a href="http://web.archive.org/web/20240530223649id_/https://support.spotify.com/us/article/car-thing-discontinued/">Wayback 2024-05-30</a>；<a href="https://techcrunch.com/2024/05/30/spotify-begins-offering-car-thing-refunds-as-it-faces-lawsuit-over-bricking-the-streaming-device/">TechCrunch, 2024</a>）。兩案的退款都是公告之後才加上的；快照只能界定退款文字出現在哪兩個日期之間，退款與抗議或訴訟之間的因果，一手來源沒有寫明。</p>
<p>FTC 對 Revolv 的調查結論說明了補償在監理上的份量。FTC 決定不採取執法行動，考量的因素包括「the limited number of units sold; Nest&rsquo;s practice of providing full refunds after the Revolv system shutdown was announced」，以及透過網站、app 內通知與信件公告退款（<a href="https://www.ftc.gov/system/files/documents/closing_letters/nid/160707nestrevolvletter.pdf">FTC 結案信, 2016</a>）。Revolv 的關閉日期在各份文件裡不一致，所以上面的公告期只能寫成約十一週：結案信第一頁寫 2016 年 6 月 19 日，第二頁引用官網當時寫的 5 月 15 日，官網之後又改成 5 月 22 日。</p>
<p>所以補償要和公告一起規劃，而不是等反應出現才加：退款的條件、截止日與申請管道寫在第一份公告裡。至於能不能讓裝置在沒有雲端時繼續運作，要在上線時決定，見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 的〈雲端關閉後的裝置功能：本地運作模式〉。</p>
]]></content:encoded></item><item><title>10.8 移交：資料匯出、開源與交給社群或封存機構</title><link>https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/10-system-evolution/handover-and-archival/</guid><description>&lt;p>服務結束時，提供者能把什麼交給誰，讓外部引用在提供者不再運作之後還能運作，是本篇的範圍；這裡的接手指服務端（伺服器、資料與網域）的接手，開源函式庫的維護權轉移是另一個主題，函式庫下架或棄置名稱的後果見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a> 的〈名稱的保留與回收：套件名稱、雲端資源名稱與短碼〉。外部引用（服務發出去、存在別人手上的網址、端點、名稱與身分）的定義與持有者的分類見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者&lt;/a>；停止新建、唯讀、靜態封存這些分階段降級的做法見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>；移交能做到什麼程度，大半在上線時就決定了，見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間&lt;/a>。&lt;/p>
&lt;h2 id="移交的對象與各自需要的資料程式碼與權限">移交的對象與各自需要的資料、程式碼與權限&lt;/h2>
&lt;p>移交的對象有四種，每一種需要的不同：&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>直接客戶&lt;/td>
 &lt;td>自己的資料，以別人讀得懂的格式匯出&lt;/td>
 &lt;td>搬到另一個服務或自己架設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開源程式碼的使用者&lt;/td>
 &lt;td>服務端程式碼，以及讓它跑起來所需的全部元件&lt;/td>
 &lt;td>自己架一份相容的服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>接手的社群或公司&lt;/td>
 &lt;td>程式碼、資料、客戶端改連新伺服器的方法與客戶端信任的金鑰或數位憑證、網域與信件服務等營運資產的交接清單，最好還有網域或它的技術控制&lt;/td>
 &lt;td>以同一套客戶端繼續提供服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>封存機構&lt;/td>
 &lt;td>讓引用繼續解析所需的對照資料，以及網域的技術控制&lt;/td>
 &lt;td>讓舊引用繼續運作，或至少保留可查詢的紀錄&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>四種對象對應到持有者的類別（&lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者&lt;/a>）：資料匯出與開源照顧直接客戶；社群接手照顧直接客戶（包括買了提供者硬體的人）與間接使用者；在四種對象裡，不需要拿到網域就以第三方持有者為目的的只有封存機構（拿到網域的接手者也照顧第三方持有者；表外還有 CDN 業者提供的鏡像，例子見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a> 的〈網域的出售：polyfill.io 的經過〉），因為第三方持有者（和提供者沒有關係、聯絡不到的引用持有者）不會自己採取任何動作。提供者自己能為他們做的靜態封存見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期&lt;/a>。表中「網域的技術控制」指代管網域的 DNS 與回應內容、所有權不移轉，見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a> 的〈交給中立方的技術控制〉。&lt;/p>
&lt;h2 id="交給直接客戶的資料匯出">交給直接客戶的資料匯出&lt;/h2>
&lt;p>匯出是最基本的移交，前提有兩個：客戶在公告期內有時間執行匯出，以及匯出的格式在另一個服務或自架環境裡讀得進去。&lt;/p>
&lt;p>Parse 在 2016 年宣布關閉時提供的資料庫遷移工具，把匯出做成不停機的線上搬遷：先建立快照，再持續同步寫入，讓開發者驗證之後才確認切換，「this can happen without downtime」（&lt;a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/moving-on/">Parse, 2016&lt;/a>；搬遷步驟見 &lt;a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12&lt;/a>）。它也把搬遷拆成兩件可以分開做的事：資料先搬到開發者自己的 MongoDB，客戶端照舊連線 &lt;code>api.parse.com&lt;/code>，由 Parse 託管的 API 伺服器改去讀寫開發者自己的資料庫；客戶端改連自己的伺服器是下一步。拆開之後，資料留在 Parse 手上這件最急的事可以先解決，等使用者更新 app 這件最慢的事可以晚一點。&lt;/p>
&lt;p>匯出的範圍要涵蓋讓外部引用繼續運作的全部資料，而不只是客戶在介面上看得到的部分。Parse 的託管檔案放在 Parse 自己的 S3 儲存，「our S3 bucket will also be turned down, which means those files will need to be migrated before that date」（&lt;a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/hosting-files-on-parse-server/">Parse, 2016&lt;/a>），所以檔案要另外用一個工具搬。goo.gl 在 2018 年讓帳號持有人從管理介面匯出自己的連結資料（&lt;a href="https://web.archive.org/web/2018040100000/https://developers.googleblog.com/2018/03/transitioning-google-url-shortener.html">Google, 2018&lt;/a>），匯出的是對照表，但網域留在 Google 手上：客戶拿到對照表之後，能做的是在自己的網域上重建連結，原本 &lt;code>goo.gl&lt;/code> 開頭的引用仍然依賴 Google。匯出期結束之後，讓引用繼續運作所需的資料（例如靜態封存保留的短網址對照表）依服務條款保留，其餘客戶資料依合約與適用的個人資料規定刪除，備份也在範圍內；刪除的時點寫進公告，客戶才知道匯出的最後期限；刪除的證據怎麼留，見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.11 資料駐留、刪除與證據鏈&lt;/a>。匯出範圍也包括帳號憑證：密碼雜湊連同演算法、參數與 pepper（如果有）一起交出，客戶的使用者才不必全部重設密碼。&lt;/p>
&lt;h2 id="開源程式碼獨立運作所需的元件與授權">開源程式碼獨立運作所需的元件與授權&lt;/h2>
&lt;p>開源服務端程式碼讓客戶與社群能自己架一份相容的服務。它的價值取決於釋出的程式碼是不是完整到能跑起來，而這一點常常受限於上線時採用的第三方元件。&lt;/p>
&lt;p>Parse 開源的 Parse Server 是可以跑的例子：它以 Node.js 實作了大部分的 Parse API，宣布同一天就釋出，之後 Parse Dashboard 也開源，至今由社群在 &lt;code>parse-community&lt;/code> 組織下維護（&lt;a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/">Parse, 2016&lt;/a>；&lt;a href="https://github.com/parse-community/parse-server">GitHub parse-community/parse-server&lt;/a>）。它也有沒涵蓋的部分：同一篇公告列出分析與遠端設定（Parse Config）這兩項功能不在 Parse Server 的支援範圍裡。&lt;/p>
&lt;p>Google 在 2025 年釋出 PebbleOS 原始碼則是另一種結果：移除了部分專有程式碼，主要是晶片支援與藍牙堆疊，所以釋出的版本「will not compile or link as released」（&lt;a href="https://opensource.googleblog.com/2025/01/see-code-that-powered-pebble-smartwatches.html">Google Open Source Blog, 2025&lt;/a>）。釋出仍然有用，社群拿到了建置系統與大部分程式；但要讓它跑起來，缺的那幾塊得有人重寫。&lt;/p>
&lt;p>開源能不能當作移交手段，取決於讓這份程式碼跑起來的全部元件裡，有多少的授權允許一起釋出。授權不允許的元件只有兩種去處：在釋出前換成可以釋出的替代品，或在公告裡寫明缺了什麼，讓接手的人知道要補哪裡。&lt;/p>
&lt;h2 id="交給接手的社群或公司">交給接手的社群或公司&lt;/h2>
&lt;p>社群或另一家公司接手，是讓直接客戶（包括買了提供者硬體的人）與間接使用者（客戶的 app 或裝置的使用者）繼續使用服務的做法，前提是接手者能讓既有的客戶端改連到新伺服器。&lt;/p>
&lt;p>Pebble 的雲端服務由社群專案 Rebble 接手，接手需要的條件在這個案例裡大多是提供者事先留下的。Pebble 在 2016 年 12 月宣布團隊轉入 Fitbit 時就寫明，之後要「phase out cloud services, providing the ability for the community to take over, where possible」（&lt;a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2016/12/06/developer-community-update/">Pebble 開發者部落格, 2016&lt;/a>）；2017 年的手機 app 更新讓使用者能用一個連結改寫服務設定的來源（&lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間&lt;/a> 的〈客戶端讀取服務位置的端點間接層〉）；2018 年 1 月把停止日延後半年到 2018 年 6 月 30 日（&lt;a href="https://dev.fitbit.com/blog/2018-01-24-pebble-support/">Fitbit, 2018&lt;/a>）。Rebble 公布接手計畫時感謝 Fitbit 在轉換規劃上的協作，並說明使用者只要在手機上點一個連結就會切換到 Rebble 的服務（&lt;a href="https://rebble.io/2018/02/15/rebble-web-services.html">Rebble, 2018&lt;/a>；&lt;a href="https://rebble.io/2018/06/13/get-ready-to-rebble.html">Rebble, 2018-06&lt;/a>）。Pebble 原創辦人 Eric Migicovsky 在 2025 年回顧時寫道，Rebble 在 2017 年、伺服器關閉之前就封存並開始託管一份應用程式商店的副本（&lt;a href="https://ericmigi.com/blog/re-introducing-the-pebble-appstore">Eric Migicovsky, 2025&lt;/a>）。&lt;/p></description><content:encoded><![CDATA[<p>服務結束時，提供者能把什麼交給誰，讓外部引用在提供者不再運作之後還能運作，是本篇的範圍；這裡的接手指服務端（伺服器、資料與網域）的接手，開源函式庫的維護權轉移是另一個主題，函式庫下架或棄置名稱的後果見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈名稱的保留與回收：套件名稱、雲端資源名稱與短碼〉。外部引用（服務發出去、存在別人手上的網址、端點、名稱與身分）的定義與持有者的分類見 <a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a>；停止新建、唯讀、靜態封存這些分階段降級的做法見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>；移交能做到什麼程度，大半在上線時就決定了，見 <a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a>。</p>
<h2 id="移交的對象與各自需要的資料程式碼與權限">移交的對象與各自需要的資料、程式碼與權限</h2>
<p>移交的對象有四種，每一種需要的不同：</p>
<table>
  <thead>
      <tr>
          <th>對象</th>
          <th>要拿到什麼</th>
          <th>拿到之後能做什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>直接客戶</td>
          <td>自己的資料，以別人讀得懂的格式匯出</td>
          <td>搬到另一個服務或自己架設</td>
      </tr>
      <tr>
          <td>開源程式碼的使用者</td>
          <td>服務端程式碼，以及讓它跑起來所需的全部元件</td>
          <td>自己架一份相容的服務</td>
      </tr>
      <tr>
          <td>接手的社群或公司</td>
          <td>程式碼、資料、客戶端改連新伺服器的方法與客戶端信任的金鑰或數位憑證、網域與信件服務等營運資產的交接清單，最好還有網域或它的技術控制</td>
          <td>以同一套客戶端繼續提供服務</td>
      </tr>
      <tr>
          <td>封存機構</td>
          <td>讓引用繼續解析所需的對照資料，以及網域的技術控制</td>
          <td>讓舊引用繼續運作，或至少保留可查詢的紀錄</td>
      </tr>
  </tbody>
</table>
<p>四種對象對應到持有者的類別（<a href="/blog/backend/10-system-evolution/service-termination-scope/" data-link-title="10.4 服務終止的範圍：外部引用的種類與持有者" data-link-desc="服務本身要結束時，盤點它發出去、存在別人手上的外部引用：網址、寫在別人程式或韌體裡的端點、被嵌入的程式碼網址、名稱與綁在網域上的身分；持有者依聯絡得到的程度分成直接客戶、間接使用者與第三方持有者；引用在服務結束後的結局：失效或易手">10.4 服務終止的範圍：外部引用的種類與持有者</a>）：資料匯出與開源照顧直接客戶；社群接手照顧直接客戶（包括買了提供者硬體的人）與間接使用者；在四種對象裡，不需要拿到網域就以第三方持有者為目的的只有封存機構（拿到網域的接手者也照顧第三方持有者；表外還有 CDN 業者提供的鏡像，例子見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈網域的出售：polyfill.io 的經過〉），因為第三方持有者（和提供者沒有關係、聯絡不到的引用持有者）不會自己採取任何動作。提供者自己能為他們做的靜態封存見 <a href="/blog/backend/10-system-evolution/staged-shutdown/" data-link-title="10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期" data-link-desc="服務結束時把一次全部關閉拆成幾個階段：停止新建、唯讀、靜態封存與關閉各保留什麼、對哪一類持有者有效、維運成本降到哪裡；依使用紀錄決定保留範圍；推動客戶遷移的手段；以及公告期長度、通知管道與補償的安排">10.7 分階段退場：停止新建、唯讀、靜態封存、關閉與公告期</a>。表中「網域的技術控制」指代管網域的 DNS 與回應內容、所有權不移轉，見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈交給中立方的技術控制〉。</p>
<h2 id="交給直接客戶的資料匯出">交給直接客戶的資料匯出</h2>
<p>匯出是最基本的移交，前提有兩個：客戶在公告期內有時間執行匯出，以及匯出的格式在另一個服務或自架環境裡讀得進去。</p>
<p>Parse 在 2016 年宣布關閉時提供的資料庫遷移工具，把匯出做成不停機的線上搬遷：先建立快照，再持續同步寫入，讓開發者驗證之後才確認切換，「this can happen without downtime」（<a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/moving-on/">Parse, 2016</a>；搬遷步驟見 <a href="https://web.archive.org/web/20161231233921/https://parse.com/migration">Parse 遷移指南，Wayback 2016-12</a>）。它也把搬遷拆成兩件可以分開做的事：資料先搬到開發者自己的 MongoDB，客戶端照舊連線 <code>api.parse.com</code>，由 Parse 託管的 API 伺服器改去讀寫開發者自己的資料庫；客戶端改連自己的伺服器是下一步。拆開之後，資料留在 Parse 手上這件最急的事可以先解決，等使用者更新 app 這件最慢的事可以晚一點。</p>
<p>匯出的範圍要涵蓋讓外部引用繼續運作的全部資料，而不只是客戶在介面上看得到的部分。Parse 的託管檔案放在 Parse 自己的 S3 儲存，「our S3 bucket will also be turned down, which means those files will need to be migrated before that date」（<a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/hosting-files-on-parse-server/">Parse, 2016</a>），所以檔案要另外用一個工具搬。goo.gl 在 2018 年讓帳號持有人從管理介面匯出自己的連結資料（<a href="https://web.archive.org/web/2018040100000/https://developers.googleblog.com/2018/03/transitioning-google-url-shortener.html">Google, 2018</a>），匯出的是對照表，但網域留在 Google 手上：客戶拿到對照表之後，能做的是在自己的網域上重建連結，原本 <code>goo.gl</code> 開頭的引用仍然依賴 Google。匯出期結束之後，讓引用繼續運作所需的資料（例如靜態封存保留的短網址對照表）依服務條款保留，其餘客戶資料依合約與適用的個人資料規定刪除，備份也在範圍內；刪除的時點寫進公告，客戶才知道匯出的最後期限；刪除的證據怎麼留，見 <a href="/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.11 資料駐留、刪除與證據鏈</a>。匯出範圍也包括帳號憑證：密碼雜湊連同演算法、參數與 pepper（如果有）一起交出，客戶的使用者才不必全部重設密碼。</p>
<h2 id="開源程式碼獨立運作所需的元件與授權">開源程式碼獨立運作所需的元件與授權</h2>
<p>開源服務端程式碼讓客戶與社群能自己架一份相容的服務。它的價值取決於釋出的程式碼是不是完整到能跑起來，而這一點常常受限於上線時採用的第三方元件。</p>
<p>Parse 開源的 Parse Server 是可以跑的例子：它以 Node.js 實作了大部分的 Parse API，宣布同一天就釋出，之後 Parse Dashboard 也開源，至今由社群在 <code>parse-community</code> 組織下維護（<a href="http://web.archive.org/web/2017id_/http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/">Parse, 2016</a>；<a href="https://github.com/parse-community/parse-server">GitHub parse-community/parse-server</a>）。它也有沒涵蓋的部分：同一篇公告列出分析與遠端設定（Parse Config）這兩項功能不在 Parse Server 的支援範圍裡。</p>
<p>Google 在 2025 年釋出 PebbleOS 原始碼則是另一種結果：移除了部分專有程式碼，主要是晶片支援與藍牙堆疊，所以釋出的版本「will not compile or link as released」（<a href="https://opensource.googleblog.com/2025/01/see-code-that-powered-pebble-smartwatches.html">Google Open Source Blog, 2025</a>）。釋出仍然有用，社群拿到了建置系統與大部分程式；但要讓它跑起來，缺的那幾塊得有人重寫。</p>
<p>開源能不能當作移交手段，取決於讓這份程式碼跑起來的全部元件裡，有多少的授權允許一起釋出。授權不允許的元件只有兩種去處：在釋出前換成可以釋出的替代品，或在公告裡寫明缺了什麼，讓接手的人知道要補哪裡。</p>
<h2 id="交給接手的社群或公司">交給接手的社群或公司</h2>
<p>社群或另一家公司接手，是讓直接客戶（包括買了提供者硬體的人）與間接使用者（客戶的 app 或裝置的使用者）繼續使用服務的做法，前提是接手者能讓既有的客戶端改連到新伺服器。</p>
<p>Pebble 的雲端服務由社群專案 Rebble 接手，接手需要的條件在這個案例裡大多是提供者事先留下的。Pebble 在 2016 年 12 月宣布團隊轉入 Fitbit 時就寫明，之後要「phase out cloud services, providing the ability for the community to take over, where possible」（<a href="http://web.archive.org/web/2017id_/https://developer.pebble.com/blog/2016/12/06/developer-community-update/">Pebble 開發者部落格, 2016</a>）；2017 年的手機 app 更新讓使用者能用一個連結改寫服務設定的來源（<a href="/blog/backend/10-system-evolution/exit-ready-design/" data-link-title="10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間" data-link-desc="服務上線時就決定、越接近關閉越難補上的設計：客戶端讀取服務位置的間接層、沒有雲端時裝置還能做什麼、外部引用落在提供者還是客戶的命名空間、使用紀錄與可交出的資料格式，以及依賴第三方契約與授權而無法移交的功能">10.5 退場能力的設計前置：端點間接層、本地運作模式與引用的命名空間</a> 的〈客戶端讀取服務位置的端點間接層〉）；2018 年 1 月把停止日延後半年到 2018 年 6 月 30 日（<a href="https://dev.fitbit.com/blog/2018-01-24-pebble-support/">Fitbit, 2018</a>）。Rebble 公布接手計畫時感謝 Fitbit 在轉換規劃上的協作，並說明使用者只要在手機上點一個連結就會切換到 Rebble 的服務（<a href="https://rebble.io/2018/02/15/rebble-web-services.html">Rebble, 2018</a>；<a href="https://rebble.io/2018/06/13/get-ready-to-rebble.html">Rebble, 2018-06</a>）。Pebble 原創辦人 Eric Migicovsky 在 2025 年回顧時寫道，Rebble 在 2017 年、伺服器關閉之前就封存並開始託管一份應用程式商店的副本（<a href="https://ericmigi.com/blog/re-introducing-the-pebble-appstore">Eric Migicovsky, 2025</a>）。</p>
<p>Insteon 是沒有任何移交安排的對照案例。2022 年 4 月經營公司停止營運、伺服器關閉，官網隨後貼出的說明只談公司的財務狀況與資產處分。兩個月後，一群 Insteon 使用者買下這個品牌與服務，新公司寫道「Our first priority was getting the hubs online immediately before we had access to this site, the email service provider, social accounts, etc.」（<a href="https://www.insteon.com/blogs/news/a-new-day-for-insteon">Insteon, 2022</a>）：接手者要先自己摸清楚怎麼讓集線器（連接家中開關與感測器、再連上雲端的裝置）重新上線，官網、信件服務與社群帳號都是之後才拿到的。服務恢復之後改成了付費訂閱（<a href="https://www.insteon.com/blogs/news/customer-support-google-assistant-and-product-updates">Insteon, 2022-06</a>，現行頁面）。</p>
<p>兩案的差別說明接手者需要提供者事先留下的條件：客戶端改連新伺服器的方法與它信任的金鑰或數位憑證、服務端的程式碼與資料，以及網域、信件服務、帳號這些營運資產的交接清單。這些都準備好時，交接可以在停止日之前完成；什麼都沒準備時，接手者得在服務已經中斷的情況下從頭重建。</p>
<p>營運資產清單要列進行銷追蹤後台。網站頁面裡嵌著的 Google Tag Manager（GTM）容器 ID、Google Analytics 4（GA4）評估 ID 與 Meta Pixel ID 都屬於原公司的帳戶，接手者拿到網站與網域、卻沒拿到這些帳戶的管理權時，頁面上的追蹤碼會繼續把訪客的事件送進原公司的帳戶，而接手者看不到這些資料、也改不了容器裡的設定；持有原 GTM 帳戶發布權限的人，還能透過頁面上的容器片段在接手者的網站上執行程式碼，原 GTM 帳戶日後再轉手給別人時，這個權限也跟著落到新的持有者手上。所以接手當下就把頁面上的容器片段換成接手者自己帳戶的容器，不等帳戶移交談完。</p>
<p>這三個後台的最上層帳戶（Meta 的商家資產管理組合、GTM 帳戶、GA4 帳戶）都能用加入接手者、再移除原公司成員的方式移交（依三家的權限規則推出），而這個方式成立的前提是帳戶裡只放這項服務的資產；帳戶裡還放著原公司其他事業的資產時，原公司不會把整個帳戶交出去，就要逐項看每個資產能不能搬到接手者的帳戶。三家的資產各有不能搬的情形，例如 Meta Pixel 在 Meta 後台對應的資料集不能轉移，搬不出來的由接手者另建、再換掉頁面上的 ID。綁在法人身上的資料也不隨成員移交（由權限設計推出）：Meta 組合的商家驗證是以原公司的身分做的，付款方式仍留在組合裡而它屬於原公司，GA4 帳戶已接受的資料處理條款也是原公司簽的，接手者要以自己的公司重做驗證、換上自己的付款方式並重新確認條款。移交的順序與前置條件見<a href="/blog/devops/monitoring/vendors/tracking-account-ownership/" data-link-title="追蹤資產的歸屬：Meta、GTM 與 GA4 的擁有層級、權限分級與移交方式" data-link-desc="Meta Pixel 的資料集、GTM 容器與 GA4 資源（property）各自屬於哪一層的擁有者，三家的權限怎麼分級，哪些資產能換到另一個擁有者底下，以及公司自營、委託代理商、換代理商與事業轉手時的歸屬安排">追蹤資產的歸屬</a>的〈資產與權限的移交方式〉中事業轉手那一段，各家能不能搬見同篇〈追蹤資產換擁有者的條件〉。</p>
<p>接手者拿到網域時，持有者手上的引用就換了主人，對持有者來說同樣是易手。<strong>公開移交</strong>指三件都做到的移交：提供者事先公告接手者的名稱；在移交條款裡寫明延續服務的範圍與期限；寫明網域或它的技術控制再次轉手時，條款是否跟著延續。公開移交讓持有者事先知道新主人是誰，但條款本身沒有被執行的機制（tr.im 的例子見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈交給中立方的技術控制〉），所以它降低的是新主人看不見的風險，不保證接手者做到。Pebble 交給 Rebble 是事先安排的接手，那一案沒有移交網域，使用者是點一個連結把手機 app 改指到 Rebble 的伺服器。</p>
<h2 id="交給封存機構">交給封存機構</h2>
<p>封存機構照顧的是第三方持有者：讓服務結束前發出去的外部引用繼續解析，或至少讓人查得到它原本指向哪裡。取得對照資料有兩種方式，差別在業者有沒有參與，這決定了能保住的範圍。</p>
<p><strong>業者合作交出</strong>：2009 年由短網址業者組成、Internet Archive 管理的 301Works，參與的業者「will provide regular backups of their URL mappings」，關站時把網域的技術控制交給 301Works 繼續轉址（<a href="https://web.archive.org/web/20091114063044/http://www.301works.org/post/240736199/url-shorteners-working-with-internet-archive-for">Internet Archive, 2009</a>）。條款同時考慮了隱私：查詢時要拿著短網址，一次只回一筆（<a href="https://web.archive.org/web/20091116124902/http://www.301works.org/post/220152694/terms-of-participation">301Works 參與條款</a>）。這個方式的效果取決於業者是否真的上傳；Internet Archive 上的 301Works 收藏至今標示為不公開取用（<a href="https://archive.org/metadata/301works">Internet Archive 中繼資料</a>），公開資料裡也沒有找到任何參與業者關站時交出網域技術控制的例子。</p>
<p><strong>外部爬取</strong>：ArchiveTeam 的 URLTeam 不等業者合作，用分散式爬蟲對短碼空間逐一送出請求、記下轉址的目的地，結果公開發布。它的說明頁寫道「Even 301Works founding member bit.ly does not actually share their databases and most other big shorteners don&rsquo;t share theirs either」（<a href="https://wiki.archiveteam.org/index.php/URLTeam">ArchiveTeam URLTeam</a>）。爬取不需要業者同意，但只能拿到爬得到的部分：短碼是不是循序產生、業者有沒有限制請求速率，決定了能保住多少對照資料。短碼的可猜測性和 <a href="/blog/backend/00-service-selection/url-shortener-implementation/" data-link-title="0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄" data-link-desc="以需求已定案為前提的實作取捨：建立與轉址兩條路徑的流量形狀、各種短碼生成方式的可猜測性與協調成本、轉址狀態碼與各層快取怎麼影響停用或修改連結多久生效、點擊事件的非同步紀錄、濫用防護、服務結束時的靜態轉址與網域去向，以及換掉任一項需求決定時要重選的實作位置">0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄</a> 的〈短碼生成：可猜測性與協調成本的取捨〉是同一件事的兩面：短碼越難猜，承載私密連結越安全，關站時外部也越難把對照表保住。</p>
<p>所以想讓封存機構接手的服務，要在上線期間就定期交出對照資料，並在條款裡寫好關站時網域技術控制的去向；等到關站時才聯絡，封存機構拿到的只會是爬得到的那一部分。</p>
<h2 id="交不出去的功能與引用第三方契約授權限制與提供者網域">交不出去的功能與引用：第三方契約、授權限制與提供者網域</h2>
<p>有些功能與引用無論提供者多配合都交不出去，退場公告要明寫它們會消失：</p>
<ul>
<li><strong>依賴提供者與第三方契約的功能</strong>：Pebble 在 iOS 上回覆簡訊的功能依賴與電信商的協議，時間軸的即時推送只有 Fitbit 能提供，Rebble 都接不走（<a href="https://rebble.io/2018/02/15/rebble-web-services.html">Rebble, 2018</a>）。</li>
<li><strong>授權不允許釋出的程式碼</strong>：PebbleOS 的晶片與藍牙程式碼。</li>
<li><strong>提供者網域下的引用</strong>：除非網域本身或它的技術控制也交出去（以本篇〈交給接手的社群或公司〉的公開移交交出；各種去向的比較見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a> 的〈網域在服務結束後的去向：續約並回應、續約不回應、到期、出售〉），否則接手者只能在新網域上重建服務，舊的引用仍指向原本的網域。提供者網域的處置見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a>。</li>
</ul>
<p>短網址服務在這幾項裡只碰到提供者網域下的引用，但它正好是短網址的全部價值所在：對照表可以交出去，引用卻寫在提供者的網域上。短網址服務的退場實作見 <a href="/blog/backend/00-service-selection/url-shortener-implementation/" data-link-title="0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄" data-link-desc="以需求已定案為前提的實作取捨：建立與轉址兩條路徑的流量形狀、各種短碼生成方式的可猜測性與協調成本、轉址狀態碼與各層快取怎麼影響停用或修改連結多久生效、點擊事件的非同步紀錄、濫用防護、服務結束時的靜態轉址與網域去向，以及換掉任一項需求決定時要重選的實作位置">0.24 短網址服務的實作：短碼生成、對照表儲存、轉址快取與點擊紀錄</a> 的〈服務結束時的轉址延續〉。</p>
]]></content:encoded></item></channel></rss>