清單過時的代價要落在精度、不落在覆蓋
論述基礎與限制
本卡從一套自建流量統計的自動化訪客辨識抽出。
辨識手段之一是比對 User-Agent 裡的自我申報名稱。第一版只回傳「有命中/沒命中」,結果是知道訪客自稱是機器人、不知道是哪一隻——無從分辨來的是搜尋引擎索引、AI 檢索、社群平台的連結預覽還是抓取工具,而這三類的處置方向完全不同。
改進時最直覺的做法,是列一份已知爬蟲的名單做精確比對。那個做法有一個當下不會顯現的失效模式:名單一定會過時,而過時的表現是新出現的爬蟲完全不被標記。統計上的呈現是自動化流量比例逐漸下降——這個下降與「內容吸引到更多真人」在數字上不可區分,而後者是所有人都希望看到的解釋。
實際採用的是兩層結構:先比對具名清單、未命中時退回通用字樣比對(bot / crawler / spider / headless)。清單過時的代價因此落在「不知道是誰」,而非「不知道有」。
證據狀態與姊妹卡 #250 不同,要分開說:那張卡的失效已經發生過兩次,本卡描述的失效在該系統上尚未被觀測到。清單才剛建立、還沒有機會過時;兩層結構是在設計當下依推演採用的,而不是被一次事故逼出來的。這不減損論證,但讀者有權知道哪些部分靠觀測、哪些靠推理。
一個現成的旁證來自同一份清單本身:它上線時含有一個永遠不會命中的條目——那是 robots.txt 的控制 token,不會出現在任何 User-Agent 字串裡。這個條目從第一天起就是死的,而系統不會回報「某個條目從未命中」,它是靠一次外部查核才浮現。清單的缺陷不產生訊號,這一點在清單過時之前就已經成立。
限制有兩層。第一層是用途:本卡談的是清單用於偵測的場合;清單用於分類時(把已知項目歸位、其餘歸「其他」)沒有這個問題,因為「其他」這一類本來就承接未知——關鍵差別在於是否存在一個接住漏網之魚的預設路徑。第二層是對象的成員資格由誰控制:「清單會過時」是經驗宣稱而非定義性真理,它取決於有沒有東西阻止新成員出現。自己完全控制的內部識別集合(自家定義的事件名、自家的錯誤碼)不會在無人知曉的情況下長出新成員,那裡加退回層是多餘的複雜度。
判斷的問題是「誰控制成員資格、有沒有東西阻止新增」,不是「上一次新增是什麼時候」——後者是落後指標,一個域可以安靜三年之後一次加十個。已凍結的協定值符合前一個問題(有規範阻止新增),而國碼與法定分類不符合(制裁名單與稅則逐年更新,後者還帶對抗性),儘管它們的變動看起來很慢。
核心原則
依賴清單的判定,要先決定清單失效之後還剩下什麼。 清單會過時是確定的,設計上的自由度在於選擇過時的代價落在精度還是覆蓋——避免過時本身不在選項裡。
覆蓋的損失不可見,精度的損失可見。 少一個分類標籤這件事在報表上看得到——標記成 ua:bot 而非 ua:claudebot,讀報表的人立刻知道自己不知道是誰。漏掉整隻爬蟲則表現為數字變好看,而變好看的數字不會促使任何人去查為什麼。
清單過時不會產生通知。 清單的維護需要外部觸發,而「有新爬蟲出現了」這個事件不會主動送達使用清單的系統。仰賴定期審視的維護計畫,在系統運作正常、沒有任何異狀的期間最容易被延後。
退回層必須獨立於清單,而不是清單的擴充。 用更多同類條目擴充清單只是把邊界往外推,邊界仍然是列舉出來的。退回層要用另一種特徵——具名清單比對的是身分,通用比對匹配的是類別詞,後者不需要知道任何一隻爬蟲的名字就能運作。
修法
判定分兩層,各自負責一件事:精確層負責識別,粗略層負責覆蓋。 粗略層的規則要寫得比精確層寬,且不引用精確層的任何條目。實作上是「先試具名比對,未命中則試通用比對」,兩者的比對來源不同,因此精確層的過時不影響粗略層的能力。
退回層的命中率要可觀測。 記錄下來的標記要能分辨命中的是哪一層。退回層命中比例上升,代表清單該更新了——這把「清單過時」從一件不可見的事,變成一個可以看的數字,而且它不需要有人知道漏了哪些新項目。
分辨清單的用途是分類還是偵測。 分類可以只靠清單,未列到的歸「其他」不影響正確性。偵測不行,因為偵測的失敗形態就是漏掉,而漏掉的東西不會出現在任何一欄裡。同一份清單在兩種用途下的設計要求不同,混用時會把分類的寬容度帶進偵測。
用最舊的清單狀態做一次推演。 假設清單從今天起不再更新,一年後這套判定還能回答什麼問題。答得出「還知道有多少自動化流量、只是不知道分別是誰」,結構就是對的;答案若是「不知道」,代表覆蓋綁在清單上,要補退回層。
判讀徵兆
某類偵測的命中數隨時間平緩下降,而沒有任何一次變更是為了減少它——先檢查判定是否綁在一份沒有更新的清單上,再接受「真的改善了」這個解釋。
判定邏輯裡只有一組正面列舉、沒有任何預設分支——這代表未列舉的一切都落進「不是」,而不是落進「未知」。
清單的更新來自偶然(讀到一篇文章、剛好看到一筆奇怪的資料),而非來自系統本身產生的訊號——這代表維護依賴外部注意力,而外部注意力不會在需要的時候到場。
跟其他原則的關係
- #250 資料多出一種形狀時,既有分析邏輯靜默換語意:同一次工作抽出的姊妹卡,兩者都描述「能力隨時間衰減而沒有訊號」。#250 的衰減由資料端變化引發(資料多出一種形狀),本卡的衰減由外部世界變化引發(世界多出一個項目);兩者的處置也同構——都是加一條接住未涵蓋部分的預設路徑,並讓那條路徑的使用量可見。
- #221 檢查規則的作用域要顯式列舉:#221 處理的是規則管哪些檔案這個 fact 被編碼進路徑常數後不再被審視,本卡處理的是判定認得哪些對象這個 fact 被編碼進清單後不再被審視。兩者的常數都會過時,而過時的表現都是「通過的數量增加」——一個是零 error,一個是零命中。
- #232 自審 sweep 的偵測方法要對齊規則類型:本卡是它在時間軸上的延伸。#232 問的是偵測方法此刻看不看得見違反形態,本卡問的是這個「看得見」能維持多久——一個在設計時成立的對齊,會因為對象演化而在事後失效。
- #125 Collapse 是隱形預設:清單型判定的預設是「沒列到就是不算」,而這個預設從未被決定過,它只是列舉式寫法的自然結果。本卡要求把那個預設拿出來明寫成退回層,屬於同一種「把沒人決定過的預設轉成有人決定過的設計」的動作。
- #236 承重論點先對抗驗證、再建下游:清單在偵測系統裡是承重的——所有下游統計都建立在「被標記的就是全部」這個宣稱上。本卡的「用最舊的清單狀態推演」是針對這個承重宣稱的一種對抗驗證,成本低到可以在設計當下就跑完。