系列:「邊做邊補:用一個 AI 代理 mesh 專案,補齊 AI 工程師該有的能力」— 第 15 篇(系列 ID:9765)
紀錄日期:2026-09-21
本機草稿,尚未貼到網站。本篇練習故障重現、測試前置條件、工具副作用與端到端驗證;不把本機候選修正當成發布完成。
上一篇用故障情境選 gateway,找到一個很適合拿來練習的問題:Spectyn 收到 partial content → error → [DONE],卻 exit 0。
這是一個協定與產品語意的差距。[DONE] 表示這條串流結束,並不表示工作完成。測試如果只看「HTTP 200」「有文字」「程序沒有 crash」,三個都可能通過,但任務仍失敗。
今天的驗收條件是:明確失敗的這一輪不能發成功 done,不能執行它的工具,也不能因前輪有成果就回報成功。
先在既有 Rust 測試檔新增三項回歸:部分文字後 error、工具片段後 error、工具形狀文字後 error。工具案例包含完整與不完整參數。
原程式跑出既有兩項 PASS、新增三項 FAIL。這才是本次修正的 RED。工具測試在 gate 計數後拒絕真正執行,因此即使舊程式有 bug,也不會為了證明 bug 去任意寫入。
這裡要測的是「根本不該抵達工具 gate」,不是最後剛好沒有檔案。
truncated 是結果上的標記,但原本後續流程仍會解析、補救工具格式並執行工具。只改標記,並沒有阻止副作用。
直接讓底層 provider 回 Err 也不夠:上層原本有一段邏輯會保留先前已執行工具的成果,最後仍可能形成成功結果。
因此這次在串流回應內帶一個內部 terminal-error 標記,讓上層先記錄 usage,再在工具解析和本輪回答儲存之前返回失敗。它不是新的對外協定,也沒有新增另一套任務儲存。
補上的第四項 Rust 測試驗證 error 後的 usage 仍入帳,error 後的 LATE 文字不再發出。合成的 100 萬輸入+50 萬輸出 token 依 gpt-4o 價格表得到 $7.50,只是測試值,不是真正花費。
單元層能證明某個分支,但 CLI 還有輸出格式、退出碼與對話檔案。於是另外建立本機 HTTP fixture,呼叫真的 Spectyn binary。
每個案例都有獨立 SPECTYN_HOME、工作目錄、合成設定與信任紀錄。七種情境分別跑 plain、JSON、quiet,共 21 組。
| 情境 | 必須看到的行為 |
|---|---|
| 健康回答 | exit 0,JSON 有 done,對話存入 COMPLETE |
| 文字後 error | exit 1,JSON 無 done,不追加成功對話 |
| 完整/不完整工具後 error | 同上,且失敗輪工具不執行 |
| 工具形狀文字後 error | 不從失敗文字補救並執行工具 |
| 前輪工具已成功,下一輪 error | 既有副作用保留、沒有重播,任務仍失敗 |
| 工具片段後 transport error | 不執行收到的工具,不重試這一輪 |
JSON 模式可能已輸出部分 token 事件,最終失敗不能撤回這些事件;目前 plain/quiet 不直接印出串流 token。判準是退出碼、完成事件與副作用;不要把「有字」當成功。
測試過程出現兩個假訊號。
第一個是 workspace 不受信任,所以工具被拒絕。沒有寫檔看起來安全,但這根本沒測到「前輪工具真的執行過」。補上隔離信任後,要同時檢查檔案內容與第二輪請求裡的 tool_result。
第二個是 fixture 以 HTTP/1.0 傳 chunked,客戶端提早拒絕,根本沒有進入想測的串流階段。正確使用 HTTP/1.1,flush 工具片段後再截斷傳輸,才能驗證失敗輪中止。
兩次修正都沒有放寬產品斷言。測試失敗不能自動推論是產品失敗,測試通過也不能自動推論情境成立。
最終 Rust 共 12 項通過,包括新錯誤回歸、既有純文字 transport/stall 契約與用量回歸。真 CLI 21 組通過。固定 FreeLLMAPI 的本機 HTTP/CLI 15 項判準通過,原本的串流假成功轉為 exit 1。
這些數字不能合成「整個產品 48 項全部安全」。各層有重疊,也有沒測到的地方:真實免費額度、模型任務品質、帳戶共享、多機競爭、跨平台、完整 gateway 背景啟動,都沒有因此獲得通過。
純文字 transport truncation 的原有行為保留;純 EOF 判斷、空錯誤串流的重試及用量、denied-tool 計數是後續獨立問題。這次也沒有提供 crash 後 exactly-once 保證。
保留 source diff、binary/harness 雜湊、原始請求與輸出、每項斷言、RED 和修正後結果。另一個 CLI 不需要相信這篇文章,可以直接重跑:
cargo test --locked --offline --manifest-path core/Cargo.toml --test stream_error_frame_failover --test stream_truncation_is_not_success --test p4_a_streamed_turn_reports_its_cost
python3 scripts/audit/stream-error-frame-check.py --binary core/target/debug/spectyn --out /tmp/spectyn-stream-check-new
輸出目錄必須是新目錄。隔離 SPECTYN_HOME 不等於作業系統 sandbox;這裡刻意只啟用合成 file_write。
證據位於 docs/specs/demo-monday-mesh/reports/evidence/m5/2026-09-21-stream-fix/。非作者審查另外記錄,timeout 或空回答不算核准。cargo check 通過;PowerShell 契約檢查缺 pwsh,因此 NOT_RUN。
今天練的是讓「完成」有可查的依據。這個能力會直接用在後面的自我開發迴圈:拆任務、做候選、測試、獨立評估,最後才討論部署和回復。
審查結果補記:Claude Code 最終 APPROVE with limits;OpenCode 最終 APPROVE,並更正初審的兩段誤讀。兩份非作者審查已齊,agy 無輸出不計入。這是本輪候選修正的審查,不是發布核准。