前一篇我把問題停在 checkpoint:當一份研究因額度、授權或其他原因中斷,系統如果能保存正文、Evidence Card、驗證結果與剩餘義務,下次就不用從頭開始。
但接著我發現,「有保存」其實還不夠。
假設某一段正文昨天通過了引用支持檢查,今天我重新取得更完整的來源,因此更新 Evidence Card,又修改了正文。資料庫裡昨天那筆 PASS 可能還在。
這時最危險的事情不是系統報錯。
而是它什麼錯都沒有,最後仍告訴我:
驗證完成,可以正式匯出。
因為從資料庫角度來看,那個 PASS 確實存在。
只是它驗證的,已經不是現在這份研究。
以前我很容易把驗證結果想成考卷上的勾勾:
citation_check = PASS
evidence_check = PASS
coherence_check = PASS
只要通過一次,就把結果存在資料庫。
但 ResearchForge 開始加入來源替換、Evidence Card、段落修訂、跨章驗證和中斷續行之後,這個模型就不夠用了。
因為真正的驗證結果應該比較像:
PASS
for:
source_revision = 18
evidence_revision = 27
document_revision = 54
policy_version = verification-v3
它不是在說:
這個專案通過了。
而是在說:
在這組特定輸入、特定版本與特定驗證規則下,這次檢查通過。
只要其中一個輸入改變,先前的結果就不一定還成立。
所以我現在比較願意把 PASS 看成一張 receipt(驗證回執),而不是永久狀態。
FAIL 其實很好理解。
例如一個段落提出「A 導致 B」,Evidence Card 卻只支持 A 與 B 同時發生,系統判定證據不足,標記 FAIL。
至少這代表系統知道問題在哪。
真正麻煩的是另一種狀態:
STALE
它代表:
我以前驗過,但驗的不是現在這一版。
例如原本正文是:
太陽黑子的數量與太陽活動週期存在明顯關聯。
經過一次修訂後變成:
太陽黑子的數量變化通常可作為觀察太陽活動週期的重要指標之一。
兩句看起來很接近。
但對研究驗證來說,它們不是同一個 claim。
第一句可能在主張「關聯」,第二句改成「觀察指標」。Evidence Card 是否仍足以支持、引用位置是否仍正確、上下文是否改變,都可能需要重新判斷。
如果系統只是看到:
claim_17 = PASS
就直接沿用,ResearchForge 的驗證就只剩形式。
所以我開始要求每一張重要回執都必須能回答:
我是驗證哪一份輸入?
最直接的方法,就是替輸入建立 identity。
例如:
document_hash
claim_hash
evidence_set_hash
source_snapshot_hash
policy_version
只有當目前輸入仍與 receipt 綁定的身分一致時,結果才能繼續使用。
否則原本的:
PASS
應該變成:
STALE
而不是繼續假裝有效。
這也是我最近開發 ResearchForge 時很常遇到的一個問題。
從使用者角度看,我可能只是改了一句話。
但系統真正需要判斷的是:
flowchart TD
A[正文修改] --> B{Claim 是否改變}
B -->|否| C[保留可能仍有效的局部回執]
B -->|是| D[Claim 驗證失效]
D --> E[重新檢查 Evidence 支持]
E --> F[重新檢查引用關係]
F --> G{是否影響章節結論}
G -->|否| H[更新局部 checkpoint]
G -->|是| I[跨段/跨章 coherence 失效]
I --> J[重新建立相關驗證]
H --> K[更新最終狀態]
J --> K
最差的做法是:
只要正文有改,就把全部驗證清空。
這很安全,但成本可能非常高。
另一個極端則是:
只重新驗修改的那一句。
這很省,但可能漏掉修改對章節結論甚至跨章推論的影響。
所以真正困難的是找到 dependency。
也就是:
這個修改到底讓哪些舊結果失效?
這也是我目前認為 ResearchForge 底層比「讓 AI 寫文章」更重要的地方之一。
另一個我最近開始特別注意的是 final summary。
以前我會覺得摘要只是方便前端顯示:
來源:44
證據卡:23
驗證:PASS
正式報告:READY
但如果這些數字和狀態只是另外算一次,就很容易出現非常危險的情況。
例如底層其實是:
citation = PASS
support = STALE
coherence = FAIL
但摘要仍保存著上一版:
overall = PASS
使用者真正看到的通常不是底層幾十個 receipt。
而是那一行:
可以匯出正式報告。
因此 final summary 不能只是 UI cache。
它應該是由目前仍有效的證據推導出的結果。
換句話說:
FINAL != previously_saved_summary
FINAL = derive(current_valid_receipts)
如果一張必要 receipt 失效,最終結果就必須跟著改。
即使這代表系統要推翻自己昨天說過的「通過」。
這件事對我來說其實很有意思。
一般軟體很喜歡追求:
PASS
READY
DONE
SEALED
因為這些狀態讓人覺得事情已經完成。
但研究系統如果把 PASS 當成不可逆的終點,反而很危險。
研究資料會變。
來源可能更新。
Evidence Card 可能重新解析。
正文可能修訂。
驗證政策也可能因為我發現漏洞而升級。
因此比較合理的狀態其實是:
PASS
↓
INPUT_CHANGED
↓
STALE
↓
REVALIDATE
↓
PASS / FAIL
也就是說:
推翻以前的 PASS,不是系統不穩定,而可能正是系統有在維持一致性的證據。
這又把問題拉回昨天談的 checkpoint。
一個可信的 checkpoint 不應該只保存:
document_v54
它至少還應該知道:
目前正文版本
目前來源快照
目前 Evidence 狀態
哪些驗證回執仍有效
哪些已 STALE
哪些工作尚未完成
目前授權與用量狀態
下一階段需要履行的 obligation
這樣下次 Resume 時,系統做的事情才不是:
我找到上一份文件了,繼續寫。
而是:
我找到上一個可信狀態,重新核對哪些前提仍成立,再決定下一步。
我把它簡化成:
Save
不是
「把東西存下來」
Checkpoint
而是
「把可以相信到什麼程度一起存下來」
這兩件事差很多。
所以我現在想像的續行流程,也從單純:
Load → Continue
變成:
Load
↓
Reconcile
↓
Invalidate stale receipts
↓
Recompute derived status
↓
Check authorization / capacity
↓
Resume
其中 Reconcile 做的就是對帳。
它會確認:
如果答案對不上,就不能直接繼續。
這也解釋了為什麼我最近在 ResearchForge 花很多時間處理看起來不像「AI 功能」的東西:revision、hash、receipt、ledger、checkpoint、reconciliation。
它們不會直接讓文章寫得更漂亮。
但沒有它們,我甚至不能確定畫面上的「完成」究竟代表哪一個版本完成。
目前 ResearchForge 已經逐漸把「驗證結果」從單純布林值拆成與輸入身分、revision 與來源狀態相關的結果,也開始區分 PASS、FAIL、STALE、BLOCKED 等不同情況。
這讓系統可以在部分輸入變動時,不必假裝舊結果仍有效,也不一定要毫無區別地把所有工作全部重跑。
但完整問題還沒有結束。
最大的難點仍是 dependency:某一張 Evidence Card、某一段正文或某一個來源變更,到底應該讓哪些跨段、跨章與最終驗證失效?如果 dependency 建得太粗,成本會暴增;如果建得太細,又可能漏掉真正受到影響的推論。
而 final summary 也必須保證只能從目前有效的底層狀態推導,不能因為歷史上曾經出現過 PASS,就一直保留 READY。
Day 28 我在處理:
系統有沒有資源承諾把事情做完?
Day 29 我開始追問另一件事:
系統現在說「完成」,究竟是在描述哪一個版本的真相?
這兩個問題其實是一組。
額度帳本要防止系統承諾自己付不起的工作;revision 與 receipt 則防止系統拿過去的結果替現在背書。
而我下一個想處理的問題,就是兩者交會之後更麻煩的一步:
當系統重新驗證後發現舊 PASS 已失效,要怎麼只重做真正受影響的部分,而不是把整份研究從頭再跑一次?
如果這件事能做好,ResearchForge 的「續行」才不只是恢復執行,而是真正開始接近一套可以長時間維護研究狀態的系統。