
昨天的發布包跑過小型負載驗證。今天把同一套訂單服務往下一步推,我想知道:如果通知沒送到,我能說清楚問題發生在哪裡嗎?
這次本機演練裡,取消訂單回了 200,健康檢查也正常,通知卻連續失敗。程式能跑,和出事時知道怎麼查,是兩件需要分別準備的事。
我先留下紀錄,再請 Claude Code 對照設計與程式找缺口。它幫我把 API 後面的背景工作展開,也指出一組這輪沒有測到的條件。
今天就沿著這張訂單,走一次從開發到接手的路。這也是第二幕的最後一道:Day 6 寫下完成條件,Day 11 把規則變成紅燈綠燈,Day 13 讓 Owner 決定能不能接受,Day 14 把必要檢查接成固定流程,Day 15 加上負載。那些都在問「做出來的東西對不對」;今天問的是「出事之後,我查得到嗎」。
我在本機啟動 r2,刻意讓假通知接收端回 503,模擬下游暫時不可用。取消 API 回 200,背景程序卻嘗試四次仍失敗,最後寫下 notify_dead_letter。
這個名稱在本服務只是「通知最後失敗」的 Log,沒有附帶持久化保存與自動補送能力。
先補一句案例背景:本篇沿用 Day 14、15 的教學服務,另外建立 r2,新增 /version 讓接手者查版本、通知模式與儲存方式。新增入口與演練腳本由作者準備,Claude 在後面參與唯讀查核。
演練腳本接下來還有一段:退回舊包 r1。先把這件事記著,等查清楚失敗發生在哪裡,再回來看退回之後剩下什麼。
如果查到 HTTP 200 就停,我只能知道 API 接受了這次操作,還不知道通知有沒有送到。
Day 10 畫圖是為了讓大家討論同一個設計。到了這裡,循序關係又派上用場:訂單狀態改完後,誰建立通知?誰放進佇列?誰負責送?最後由誰留下結果?
我請 Claude 讀當時的設計核對文字、本次請求與 Log,以及新版程式,沿著這些互動逐段對照。這輪實際輸入沒有原圖;下面是根據查核結果補畫的說明圖。

通知先放入佇列,API 才回應;背景送達與 HTTP 回應沒有固定先後,圖中的上下分區不是兩者完成的時間順序。
先從 order_id: during-update 找到取消紀錄,再用同一個 notification_id 串起背景通知。原始 Log 的重點如下,省略時間與其他欄位:
| 事件 | 紀錄裡看到什麼 | 能確認什麼 |
|---|---|---|
cancel |
result: ok、transitioned: true、queue_depth: 1 |
取消狀態已改變,通知進入佇列 |
notify_attempt_failed |
同一通知,attempt 從 0 到 3,status: 503 |
實際嘗試四次,接收端都回失敗 |
notify_dead_letter |
同一通知,attempts: 4 |
這輪已停止嘗試,仍未送達 |
Claude 再對照當時版本的 Program.cs:API 的修改狀態、建立通知、放入佇列、記 Log、回應都走完了。**五步全部成功。**失敗整段落在背景通知工作 — 重試迴圈在 Program.cs:242,四次嘗試之間等 200、400、800 毫秒,最後在 :264 寫下失敗終態。
設計文字原本就提醒過「送達與 HTTP 回應沒有固定先後」,但列出的五步只追到 API 回應。Claude 這次有用的地方,是把這句提醒展開成可以逐段核對的工作,而不是再告訴我一次通知失敗。
它也發現設計文件的行號過期了。原來標的 69、79、84、88、89,在新版已經移到 64、82、88、92、93。我核對過這些位置,不能拿舊行號直接當現在的依據。
帶回自己的專案,可以這樣請 Claude 協助。關鍵是第三行:要它逐段列出「仍無法確認的地方」,否則它會把推論寫成結論。這段是依本次查法整理的提示,不是原始實跑提示的逐字重現:
先不要修改程式。
根據這次事件的時間範圍、訂單與通知 ID,讀取設計、Log 與部署版本的程式。
沿每一段服務互動,列出:預期動作、實際紀錄、程式位置、仍無法確認的地方。
分開回答 API 是否成功、背景工作是否完成。
找不到紀錄就標示缺證據,最後提出最小的下一步查核。
執行時也要限制工具。兩輪都用 headless 跑:
claude -p "<提示>" --restricted \
--tools Read,Grep,Glob --allowedTools Read,Grep,Glob \
--setting-sources '' --strict-mcp-config --mcp-config empty-mcp.json \
--no-session-persistence --max-budget-usd 3
第二、三行是重點:沒有 Edit、沒有 Bash,MCP 指向一個空設定檔,所以它連一個外部工具都碰不到。這不是提示裡拜託它別動手,是啟動時就沒給手。 這一輪十二個回合、七十九秒、US$0.1733,execution.json 的 changed 是空陣列,逐回合軌跡完整留著。
差別在於舉證:換成在對話框裡問一句「你看看這些紀錄」,我沒辦法證明它沒有順手跑個指令、沒有讀到我以為沒給它的東西。邊界關在工具層,判讀才從意見變成可查的紀錄。
回到剛才記著的那一段。演練照腳本退回舊包 r1,同時把假接收端恢復成 200,新訂單又能完成通知 — 看起來收工了。但兩個條件是一起改的,所以恢復不能全算成換回程式的功勞。
我把演練紀錄交給 Claude,請它分開查程式、下游與故障期間的訂單。
它提出一個值得補的檢查:這輪用 r2 時,下游一直是故障狀態。那 r2 配上健康接收端,到底能不能完成通知?
(區分恢復範圍、演練通過不等於能上線,這兩個判準是我在提示裡就要求的;它自己做的是把那一格找出來。)
這輪同樣唯讀,七個回合、二十三點六秒、US$0.0659,changed 也是空陣列。它把「故障期間那張訂單」標成沒有量測,附的是 report.json:151 的 no automatic replay 與 requests.json:138 的 404,而不是從「新訂單能取消」推論已經恢復。

右上是 Claude 指出的那一格:故障注入在下游,新版配健康接收端原演練沒走過,補測十項才填上。
演練腳本的十八個檢查點全過,代表預設情境符合預期,不代表所有組合都走過。它們是同一輪演練中的斷言,不是十八個獨立案例。這張四格圖,反而比一串綠燈更容易看出還缺哪裡。
我依建議補跑 r2 加健康接收端,十項檢查全過。重點整理如下:
| 補測 | 實際結果 |
|---|---|
| 核對版本 | r2,非同步通知、記憶體儲存 |
| 取消未出貨訂單 | 回 200,狀態成功改變 |
| 確認通知送達 | notify_sent,接收端留下一筆實際收到通知的紀錄,第一次嘗試即成功 |
| 已出貨訂單要求取消 | 回 409,接收端紀錄沒有增加 |
最後一項也補上第一輪設計核對指出的缺口:原演練的六筆訂單都是未出貨,不能用那輪紀錄證明已出貨分支。這不表示前面所有測試都沒驗過,而是這輪證據回答不了。
現在能說的是:新版在這次短時、單筆情境下可以完成通知,拒絕取消時也沒有送出通知。 這十項不是負載或長時間穩定性測試,也沒有把先前失敗的通知補送出去。
演練裡的訂單放在程序記憶體。停止程序再啟動,原訂單就查不到了;換回舊包,也不會把資料帶回來。因此「新請求正常」與「故障期間那張訂單已處理」,必須分開留下。
到這裡,接手內容可以寫得很具體:
| 要交代的事 | 這次留下什麼 |
|---|---|
| 哪筆工作未完成 | during-update;通知 ID d0e39019500e489cb21d391c3f303b05 |
| 已查到哪裡 | 取消成功,通知四次收到 503,留下失敗終態 |
| 哪些已恢復 | 舊包與健康接收端恢復後,新訂單正常;另補測 r2 配健康接收端也通過 |
| 哪些還沒解決 | 原訂單查詢 404,沒有自動補送;要先查可用資料與下游結果,再決定能否補償 |
Claude 幫忙整理查核路徑、指出缺口;補測與停啟由腳本執行。它沒有替我恢復資料,也沒有決定可以重送。否則一次「幫忙處理」,可能又造成重複通知。
完整版本紀錄、十八項檢查、工具設定與補測入口放在操作附件,正文先留下接手者真正需要知道的結果。
服務恢復了,那張訂單還沒結案。下一步:誰接著查、多久要回應、沒人接時找誰?
參考資料:
days/day14/lab-delivery/ 的教學服務與實作紀錄。兩輪唯讀判讀分別在 day16-design-trace-01(設計對照,12 回合)與 day16-rollback-lab(恢復範圍,7 回合),各自保存輸入清單與雜湊、完整提示、逐回合軌跡、原始回覆與用量;補測在 runs/r2-healthy-01,腳本為 verify-r2-healthy.py。引用的行號經作者人工複查。各輪限制見操作附件。/version 造成的。r2 重新經過檢查與打包,昨天 r1 的壓測結果不算到新版上。兩輪 Claude 唯讀分析本機封存檔,沒有執行部署或補送,也沒有透過 Grafana MCP 查即時資料;工具唯讀只保證它沒動手,不保證我給的輸入完整。作者補跑 r2 健康情境十項檢查,屬短時單筆,不是負載或長時間穩定性測試。沒有正式上線、真人值班或團隊減載的驗證。