Day 17 說過三種觀念要分清楚。今天寫第一個撞上實作層的事故:一張卡在我手上永久鎖死,誰都無法再認領。
八月十四日,我照標準流程做某張卡的操作:把工作中的一件事交接給新的認領者。步驟都按規定的來。但接下來的下家不管怎麼試,得到的回應都是同一句:
Card already has a running Activity
「這張卡已經有進行中的 Activity,不能再由另一個認領。」聽起來像是暫時的暫止,可是隔了天、重新再認領、甚至重新開 session,都還是同一句。檢查看板上,這張卡原確實沒有 active 的 Activity,動作卻一直被相同理由擋下。
第一個直覺的猜測是 SessionEnd hook 沒觸發。我的系統裡,hook 負責在 session 結束時把活動狀態收尾,hook 沒跑就會留下髒狀態。手動跑那個 hook,回應是 status: ok,0.19 秒執行完,一切正常。接下來逐層檢查:hook 有裝、執行路徑正常,於是去看資料庫本身,那裡才是真相的關鍵。
排查的結果是兩條判準分別活在兩個地方。client 的前檢查看的是 activity_state 這一欄:它檢查的是不是 'running'。資料庫裡的另一道門檻是資料表的 trigger,它檢查的是 closed_at 是不是 null。交接的實作只更新了 handed_off_at,其他兩個欄位都沒動:activity_state 沒改成 paused、closed_at 沒填時間。
結果就是每個門檻看到不同的世界:前端拼字判斷拒收、資料庫的另一端也拒收。兩道門各看各的門票,而那兩張門票都真的沒被更新過,卡是永久鎖死的。
其次我發現既有的測試不允許我想做的修改。有個測試的名字就寫著:「preserve a running Activity」。它把現行行為鎖起來,還附了註解說明當時設計的理由。
我根據什麼判它是 bug 而不是有意的設計?根據 schema 而不是根據偏好:這個組合在 schema 上不可達。DB trigger 會擋掉下一次的 attach,所以「保留 active 狀態、之後讓卡可以被重新認領」那個設計目標,其實從來就無法發生。能有 schema 層面的推演,就不需要「但我是作者」的感情成分在內。
修完之後把 PR 提了上去,CI 通過,服務部署回線上。同時另一個問題浮出來:部署程序不會自動跑資料庫 migration。build 全綠、服務重啟成功,但資料庫的版本本身完全沒動,新修好的欄位語意落到線上的資料是空的。發現的那一刻,我感受到配置管理最微妙的成本:第一次是修到底但沒修到資料,第二次才意識到部署的 pipeline 缺一項手動提交。
和 migration 的部署整合還沒有全面自動化,這坑仍在辦的清單上。
事故修掉之後,我意識到下一個問題:活動有時候是因為「我」的 session 斷線而停住,不是 agent 做錯事了。該有一個人工的出口讓我救回被自己忽略的帳本。明天講這個。