昨日回顧
Day 21 把任務型 eval 接到合併門檻,要求每筆 case_id 都有結果。今天處理另一個容易失控的地方:測試資料。案例越多,不代表越可靠。如果沒人知道它從哪裡來、為什麼有這個預期結果,修改時就只能猜。
本文延續 ticket-source-guard,以虛構的購票來源資料示範,不代表真實網站已被確認,也不宣稱以下資料集已經在正式專案執行。
每個案例都要有一張身分卡
先讓一個案例回答四個問題:測什麼、從哪裡來、依據什麼、誰可以修改預期結果。
case_id: stale-registered-host
origin: synthetic
risk: stale-evidence-treated-as-current
contract_ref: freshness-policy-v2
fixture: registry-stale.json
evaluation_date: "2026-10-03"
expected_status: UNCONFIRMED
owner_role: maintainer
origin: synthetic 表示合成案例,不能寫成「使用者真實回報」。contract_ref 指向可檢查的規格版本,不是某次模型回答。固定日期、fixture 路徑與 case_id 一起構成可重現的輸入。
如果案例來自公開回報,記錄來源與授權範圍;如果來自私人對話,不因為自己看得到就直接收進公開 repository。先確認可保存、可分享的範圍,或改用足以重現邏輯的合成案例。
去識別化不能破壞被測的邊界
把私人姓名改掉還不夠。URL query 可能包含 token、訂單編號或追蹤 ID,截圖也可能帶登入資訊。能用虛構 fixture,就不存整份原文。
但遮蔽資料時要保留真正造成錯誤的結構。例如測試相似 host,不能把兩個不同 host 都換成同一個 example.test;那樣會把安全問題刪掉。
原始風險:tickets.vendor.test 與 tickets.vendor.test.attacker.test 被混淆
公開 fixture:tickets.example.test 與 tickets.example.test.other.test
保留:host 邊界、額外 suffix、預期 UNCONFIRMED
移除:私人活動名稱、訂單、cookie、真實使用者輸入
以上網域都只是測試名稱。資料轉換後要重跑案例,確認它仍然能重現原本的錯誤類型,再記錄轉換方式。
分開開發集與保留集
開發集用來修程式和指令;保留集用來觀察修正是否只迎合已知題目。兩者不能只是相同案例換一句問法。
依風險種類分組:相似網域、子網域、過期、撤銷、衝突、缺值、來源中的不可信指令。切分時把同一個錯誤的近似變體放在同一組,避免同一題的答案透過變體洩漏到兩邊。
這裡的「保留」不是永遠不能看。看過並針對它修改後,就記錄它已成為開發依據,必要時補新的保留案例。不要把反覆調過的測試集稱為全新盲測。
版本變更要說清楚改了什麼
eval-set-4
新增:revoked-host-with-old-valid-record
修正:conflicting-records 的錯誤 fixture 路徑
不變:freshness-policy-v2 的狀態定義
移除:duplicate-similar-host,重複且沒有新增邊界
資料集版本、manifest digest 與候選版本都要進 Day 21 的報告。比較兩個模型或兩個 Skill 版本時,先確保它們跑的是同一組輸入。不同資料集的 95% 與 98%,不能直接當作進步證據。
更新預期結果尤其要小心。若是規格真的改了,連回 Day 19 的變更請求,留下舊規格與新規格的差異;若只是模型答錯,修模型路徑或程式,不要替它改答案。
淘汰案例也要留紀錄
案例可以因重複、過時規格或錯誤資料而退休,但每次移除都記錄理由、替代案例與審查者角色。關鍵風險不應因為難測就消失。
退休前:這筆案例保護哪個風險?
替代後:哪一筆新案例仍保護同一條界線?
若無替代:是否明確變更了支援範圍?
今天完成的驗收標準
[ ] 案例有來源、風險、規格依據與固定輸入
[ ] 合成案例與真實回報清楚區分
[ ] 去識別化後仍保留被測的安全邊界
[ ] 開發集與保留集的使用紀錄可追溯
[ ] 報告帶資料集版本與 digest
[ ] 預期結果變更與案例退休都有理由
明天預告
Day 23 把這些工程成果寫成讀者真的能使用的 README:先說範圍與限制,再給最小範例、輸出解讀與失敗處理。