Audit 失效模式分類學:31 項檢查,每項都是一次用戶生氣換來的
上一篇講了 council + judge + audit 三層架構的全貌。這篇把最後一層——
digest_audit.py的確定性稽核——整個拆開:逐項列出、按失效模式歸類,並完整解剖兩個「防線自己變成事故」的反例。
先坦白:第 31 項檢查不存在
動筆第一件事是把 31 項檢查逐一列表,結果馬上撞到一件尷尬事。上一篇說「31 項獨立命名的檢查」,查證指令是:
grep -o '"check": "[a-z_0-9]*"' digest_audit.py | sort -u | wc -l # → 31
但其中一項 tldr_has_tw 只存在於檔案開頭 docstring 的用法範例裡,程式碼裡真正會觸發的檢查名是 tldr_missing_tw。改數 fails.append 出現次數:恰好 30。也就是說,真實存在的檢查是 30 項,第 31 項是查證指令自己數出來的幽靈——稽核系統被自己的稽核方法騙了一次。這不是本篇要遮掩的瑕疵,是本篇的題眼:確定性檢查的失效模式,連「數自己有幾條」這一步都會出現。以下照實寫 30 項。
分類總表(30 項 + 1 幽靈)
severity 語義:HIGH → 觸發「等 60 秒 → 換更強模型重生 → 仍敗切純程式備援版」的完整 retry 鏈;MED/LOW → 記錄失分但照寄。共 20 項固定 HIGH、6 項 MED、2 項 LOW、2 項動態升降級。
| # | check | 類別 | severity | 一句話觸發場景 |
|---|---|---|---|---|
| 1 | tw_pre_market_tense | 時序紀律 | HIGH | 早上 7 點盤前寄的信寫「今天台股已漲/跌」(9:00 才開盤) |
| 2 | tw_pre_market_tense_zaoshen | 時序紀律 | HIGH | 「今早台股已漲」— 9:00 前不可能知道 |
| 3 | us_holiday_tense | 時序紀律 | HIGH | 美股昨晚休市,卻寫「昨晚美股收紅/收黑」 |
| 4 | tw_holiday_open_tense | 時序紀律 | HIGH | 台股休市日寫「今早 9:00 開盤」 |
| 5 | us_holiday_tonight_tense | 時序紀律 | HIGH | 美股今晚休市寫「今晚開盤」 |
| 6 | tw_morning_action_missing | 時序紀律 | MED | 台股開盤日,操作卡沒有任何「今早開盤後」動作窗口 |
| 7 | us_tonight_action_missing | 時序紀律 | MED | 美股開盤夜,操作卡沒有任何「今晚/盤後」動作指示 |
| 8 | tldr_section_missing | 覆蓋契約 | HIGH | 整份日報沒有「30 秒重點」區塊 |
| 9 | tldr_missing_tw | 覆蓋契約 | HIGH | 用戶持有台股,TLDR 完全沒提台股 |
| 10 | tldr_missing_us | 覆蓋契約 | HIGH | 美股晚報,TLDR 完全沒提美股 |
| 11 | tldr_too_short | 覆蓋契約 | HIGH/MED | TLDR 0 條 bullet(區塊全空)= HIGH;1–2 條 = MED |
| 12 | holdings_uncovered | 覆蓋契約 | HIGH | 用戶選的持股有任何一支沒拿到操作卡 |
| 13 | signal_card_missing_battle | 覆蓋契約 | HIGH | 操作卡缺進場/目標/停損三件套 |
| 14 | market_summary_missing_tw | 覆蓋契約 | MED | 用戶有台股,「大盤怎麼了」卻沒提台股大盤 |
| 15 | fake_urls | 編造偵測 | HIGH | 出現 example.com / placeholder.com 之類假網址 |
| 16 | placeholder_prices | 編造偵測 | HIGH | 佔位符沒填:「金額高達 XXX 億元」 |
| 17 | earnings_fabricated_estimates | 編造偵測 | HIGH | 資料端沒有核實預期數字,財報區卻出現「預期 EPS」 |
| 18 | confidence_overclaim | 編造偵測 | MED | 信心數字 >75%(歷史校準上限,超過=防線破口) |
| 19 | speculative_causality | 編造偵測 | LOW | 無來源歸因:「可能與 XX 有關」 |
| 20 | prompt_instruction_leak | 洩漏隔離 | HIGH | 給 LLM 的說明文字被抄進成品(「台股講…美股講…」) |
| 21 | portfolio_lens_foreign_ticker | 洩漏隔離 | HIGH | 掛「你的」的個人化區塊混進非用戶持股的真實證券 |
| 22 | ai_output_truncated | 結構完整 | HIGH | HTML 結尾像被切斷(token 超上限) |
| 23 | undefined_css_class | 結構完整 | HIGH | body 用到 <style> 沒定義的 class=整區塊裸奔 |
| 24 | tw_ticker_bare_code | 結構完整 | HIGH | 台股卡只有裸代號沒中文名(名稱表歸零訊號) |
| 25 | ticker_no_zh_name | 結構完整 | LOW | 美股代號附近沒有中英文公司名 |
| 26 | signal_reason_shallow | 深度一致 | HIGH | 理由長度 median <80 字=整批模型降級的系統性塌陷 |
| 27 | signal_reason_vague | 深度一致 | HIGH | 「下一步」既無價位也無時間窗(虛詞卡) |
| 28 | isolated_wait_phrase | 深度一致 | MED | 孤立的「先觀望」沒附條件(價位/事件/日期) |
| 29 | verdict_monoculture | 深度一致 | MED/LOW | ≥4 張卡全同向;若結論同時喊「先觀望」=自相矛盾升 MED |
| 30 | news_headline_body_conflict | 深度一致 | MED | 新聞標題「反彈 1.10%」,內文「反而下跌」 |
| 31 | 幽靈 | — | 只存在於 docstring 範例,被查證 grep 數進去,永不觸發 |
每一項都刻著一個日期
這張表最重要的性質是:幾乎每項檢查的註解裡都刻著它誕生的那次事故日期。挑幾個最有故事的:
holdings_uncovered(05-26):整套 audit 的創始契約。用戶原話「使用者選擇每一個台股美股都要顯示下一步」直接寫在註解裡。漏一支持股就是 HIGH——這是全系統最不容妥協的一條。
earnings_fabricated_estimates(06-10):日報寫 AAPL「預期 EPS 3.60」,真實資料源給的是 1.86,還附贈一句「iPhone 15 仍是重點」(當時已不是)。修法很誠實:程式知道自己「今天有沒有拿到核實的預期數字」,沒有的話,財報區出現任何「預期 EPS/市場預期」= 編的,直接 HIGH。同一天還抓到 verdict_monoculture(四張卡全喊「即刻分批買進」,結論卻說「先觀望等 CPI」)和 news_headline_body_conflict(標題「油價反彈」,內文「油價反而下跌」)——06-10 是實質稽核從「查格式」升級到「查自洽」的一天。
tw_ticker_bare_code(07-09):憑證過期 → 上櫃股名稱 API 的 SSL 失敗被靜默吞掉 → 名稱表全滅 → 主旨和卡片全部裸代號寄出。這條檢查的邏輯有意思:名稱展開是確定性後處理,「正常情況不可能失敗」,所以一旦出現裸代號,幾乎必是上游基礎設施壞了——檢查內容,實際上是在監控依賴。
prompt_instruction_leak(07-22):每張卡開頭掛著「白話講『下一步』:台股講…、美股講…該做什麼具體動作」——這是 prompt 裡給模型的說明,被逐字抄進成品。抓法不是只抓那一句,而是抓整個「meta 指令 tell」類別:佔位括號〔〕、「必含至少 N 個價位」、「禁止只寫觀望」……這些字只該活在 prompt 裡。
signal_reason_shallow(07-23):上一篇講過梗概,這裡補校準細節。免費配額全熔斷、卡片掉到弱模型,理由從正常的百餘字塌到四五十字——帶價位、格式全對,30 項裡 29 項放行,是老闆本人抓到的。修法是先拿 3 個正常日(median 107–197 字、min≥97、零卡 <80)和壞日(median 48、min 46、5/7 卡 <60)做統計基線,門檻取中間:median <80 或半數卡 <60 才判塌陷。抓「整批降級」,不誤殺單張天生短的卡。格式檢查便宜,深度檢查必須先有基線。
主菜:防線自己變成事故的完整解剖(07-06)
第 1 篇只給了梗概,這裡是完整 timeline。
06-29(週一):LLM 把「沿用既有 CSS class」自由發揮成近似名(news-title vs news-headline),樣板沒規則,區塊裸奔。當天修了兩手:補 16 個被改名 class 的 CSS,並新增 undefined_css_class HIGH 檢查——比對「body 用到的 class」和「<style> 定義的 class」的差集。看起來滴水不漏。
07-06(下一個週一,檢查上線後第一個週一):週一版 prompt 只寫「沿用平日日報 CSS class」、沒貼逐字骨架——平日版有完整骨架範例所以平日沒事,這顆雷專屬週一。各家模型(不分供應商)集體自創近似 class,12/12 位用戶全中 HIGH。retry 鏈忠實執行:換更強模型重生——但 prompt 沒變,同病再中。9 位被打成純程式備援版(「AI 個人化生成異常」極簡版)。admin 彙總告警在 07:24 送達——信已經寄完了。雪上加霜:原本 05:30 的 preflight 預跑早在排程基礎設施遷移時漏搬,已經靜默死了近一個月,沒人發現。
沒有這條檢查,07-06 用戶會收到版型跑掉的信;有這條檢查,9 位收到的是閹割備援版。防線把「醜信」換成了「殘信」,不是換成「好信」。
根治(同日):在 premailer 內聯之前加一層確定性修復 _repair_undefined_classes——12 個已知近似名直接對映回正確 class(恢復原本想要的樣式),未知 class 移除(CSS 本無規則,移除是視覺 no-op)。audit 檢查保留,但降級成最後防線,理論上永遠不會再觸發。週一 prompt 補上逐字骨架。另加系統性熔斷:同一 HIGH 檢查在一次生成中連中 3 位,立刻推播管理員——per-user 的 retry→fallback 鏈遇到系統性 bug 會「安靜地全員降級」,必須有跨用戶的橫向視角,而且告警要趕在整點寄出之前。
反例二:檢查誤殺(07-24)
portfolio_lens_foreign_ticker 的出身正當:07-21,「組合透視」區塊掛著「你的」字樣,卻混進公版觀察清單 10 檔(下一篇的主角)。防線邏輯:個人化區塊裡出現「像代號的 token」且不在用戶持股 → HIGH。
然後它開始殺良民。年份(2026)、指數點位(23150)、價位(1085)、名詞縮寫(AI/GDP/ETF)——全都「像代號」。07-24,一位用戶的日報 retry 換強模型重生,新版又踩到另一個數字,連兩版被誤殺,整封降備援。受害者是老闆本人。
根治是把判準從「像代號」改成「是真實上市證券」:台股比對約 1.2 萬檔代號快取,美股比對名稱庫。真的洩漏進來的標的(AAPL/2330)仍在名表裡照抓,防線不弱化;年份和價位不在名表,放行。啟發式白名單永遠列不完例外,真實 universe 才是對的邊界。
同一天誕生的還有「軟硬錯」分級:HIGH 檢查分成「軟錯」(內容完整正確、只是不夠深:理由偏短/偏籠統/TLDR 偏短)與「硬錯」(版面壞/假數字/裸代號/截斷/漏持股/時序錯/洩漏)。這個分級後來成為備援決策的輸入——寧可完整帶小瑕,不要為軟錯閹割。
設計準則(30 項檢查的學費總結)
- 出事 1 次=當場建偵測器,而且檢查名和註解要刻上事故日期——這張表就是系統的病歷。
- 新增 HIGH 檢查前必問三題:全員同中會怎樣(需要系統性熔斷)?誤殺誰來扛(需要真實 universe 而非啟發式)?這是軟錯還是硬錯(決定降級到備援版值不值得)?
- MED 不觸發 retry,所以 MED 靠修復層不靠重生:兩條動作窗口檢查(#6/#7)連續多日失分(07-27 起三天 11/7/11 位),prompt 遵從是隨機的——最後靠確定性改寫層根治,audit 降為獨立驗證者,兩邊共用同一條 regex 保證判準不岔開。
- 檢查自己也會壞:「9:00開盤」中間多兩個字元就 miss 的 regex(07-15 假陽性)、把價格「以1090開盤」誤認成時間的 lookbehind——所以 audit 的 regex 也有自己的驗證者案例集。以及,連「數自己有幾條檢查」都能數錯。
寧可誤殺、不可放行,是這套系統的默認立場;但 07-06 和 07-24 教會我們,這句話的完整版是:寧可誤殺,不可放行——然後為每一次誤殺建一層確定性修復,讓檢查退居最後防線。
下一篇:資料隔離事故完整報告——公版觀察清單怎麼混進「你的持股」,以及個人化系統最尷尬的失敗形態。
相關文章
MarketDaily 每天早晚用這套系統寄出日報,目前全功能限時免費——現在訂閱的早鳥用戶,未來恢復收費後永久保留免費使用權
免費訂閱 →本文僅供資訊整理,非投資建議。投資有風險,請評估自身狀況。資料更新:2026-08-12