iT邦幫忙

2026 iThome 鐵人賽

DAY 14
1
Claude AI

從 AI 助理到營運中台:金融 PM 的 30 天 Claude Code 治理實戰系列 第 14 篇

系統組裝日的「恐怖故事」:當零件全綠,接縫卻讓稽核紀錄消失時

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260928/20144604Ypcs8DniK9.jpg

引言:組裝日的定律

在軟體工程的長征中,第 14 天通常是最令人興奮也最令人恐懼的。我們花了整整五天——從 Day 9 的原則制定、Day 10 的痛點剖析,一路走到 Day 13 的例外規則定義。今天,是「零件」入庫、正式進入「流程編排區 v0.1」組裝線的日子。

然而,組裝日有一條永恆的定律:「零件各自全綠,接縫處照樣出事。」 即便單體測試跑得再順暢,當我們試圖將這些分散的邏輯縫合在一起,讓案件真正從「進件」走到「結案」時,隱藏在架構縫隙裡的魔鬼就會現身。今天,我不僅帶回了全綠的測試燈號,更帶回了兩個能讓後端架構師驚出冷汗的現場筆記。


https://ithelp.ithome.com.tw/upload/images/20260928/20144604tvb5oc5ssT.jpg

驚人發現一:發生在「兩不管地帶」的稽核紀錄湮滅事件

這是今天最震撼的失敗。我們建立這套系統的核心價值在於「留痕」與「合規」,但就在第一版組裝完成時,系統竟然在完全沒有報錯的情況下,無聲地湮滅了關鍵的稽核紀錄。

這場災難源於架構設計中的「兩不管地帶 (Two-No-Man's Land)」:

> AssertionError: 票券編號重複,第二張會覆寫第一張 E assert 'X-C-2026-0001-01' != 'X-C-2026-0001-01'

架構師的後門檢討: Day 13 設計的 open_ticket 函式帶有一個 seq 參數,預設值為 1,它理所當然地假設呼叫端會負責處理序號遞增;而 Day 14 的 run_ai_station 在呼叫時,又天真地以為票券生成器內部會自動處理唯一編號。

這兩個「合理」的假設在接縫處碰撞,導致同一個案件若觸發第二次例外(例如 AI 第一次分類失敗,人工重啟後又遇到服務中斷),產生的票券編號會與第一次完全相同。在持久化存檔時,第二次的紀錄會直接覆寫第一次。這對於稽核來說是致命的——系統「弄丟」了曾經發生過的錯誤紀錄。

修正方案: 我們不再依賴呼叫端的假設。在 run_ai_station 開票前,系統必須強制先盤點該案件既有的票券數量,確保 seq 依實際張數嚴格遞增。


https://ithelp.ithome.com.tw/upload/images/20260928/20144604Zk0xGmBtDG.jpg

驚人發現二:當測試也陷入「心理盲點」

在執行快樂路徑(Happy Path)測試時,我原本斷言整趟旅程應該留下 5 筆狀態轉換留痕,但測試結果卻鐵青地顯示:4 筆。

這是一個關於「架構師自尊」的有趣錯誤。經過排查,程式碼沒錯,錯的是我的測試期望值。我下意識地將「進件建案」這個動作算作一次狀態轉換。但回歸 Day 12 的狀態機設計,案件從「不存在」到「已收件 (Received)」屬於初始化 (Initialization),而非狀態轉換 (Transition)。

測試確實不會說謊,它只是精確地戳破了開發者在設計測試時,對自身架構邏輯的認知落差。我們最終修正了測試期望值,並在註解中深刻地補上一句:「進件不是轉換」。


https://ithelp.ithome.com.tw/upload/images/20260928/20144604kx5L4EaM9T.jpg

核心架構解析:拒絕「捷徑」的狀態機執念

為了防止「接縫失守」演變成整座架構的崩塌,flow_service 在實作上展現了近乎偏執的強制力。

我們在 flow_service 中定義了 start_processing、send_to_review、review 等核心函式,但這些函式的內部並非直接修改資料庫狀態,而是強制呼叫 Day 12 的 transition 狀態機。我們拒絕任何「把案件標成已結案」的捷徑,如果開發者試圖跳關,系統會毫不留情地拋出 TransitionError 並在畫面上噴出紅字。

此外,我們嚴格區分了操作者身分:

  • 系統步驟 (SYSTEM):如自動化分類、AI 開票。
  • 人工動作 (HUMAN):如審核核准、退回或例外接管。

這種身分鎖定機制,確保了每一筆留痕的「權責歸屬」在架構層面就無法被篡改。


https://ithelp.ithome.com.tw/upload/images/20260928/20144604ySxDvLAaOP.jpg

技術亮點:優雅的「離線測試」—— Runner 注入法

今天的另一個亮點是落實了 E4 精神:「Python 邏輯部分不依賴模型服務」。

在 AI 站(AI Station)的處理中,我們引入了「執行器 (Runner) 參數」的設計。這意味著 flow_service 根本不關心背後是哪個 Claude 模型。在測試與展示時,我們可以注入不同的虛擬執行器來模擬三種場景:

  1. 正常回傳:驗證快樂路徑。
  2. SENSITIVE_INPUT:模擬觸發敏感詞攔截,驗證自動開票。
  3. SERVICE_DOWN:模擬 API 崩潰,驗證人工接管流程。

這種設計讓我們即便在沒有 API Key 的離線環境,依然能完整跑完所有例外路徑的壓力測試。


https://ithelp.ithome.com.tw/upload/images/20260928/20144604vVLEJbCk4r.jpg

視覺化實踐:從冷冰冰的 JSON 到透明的「旅程」

我們利用 Streamlit 將這些複雜的後端狀態具象化。它不再只是顯示一個案件清單,而是一個「透明化工具」,包含四大核心:

  1. 時限監控:標示當前狀態的主體、處理期限與逾時升級對象。
  2. 逐筆留痕:清清楚楚記錄「誰、以什麼身分、為什麼」執行了轉換。
  3. 例外票券:包含原因碼、類別與接手角色。
  4. 動態動作區:這是狀態機的實體化——只有在對的狀態,才會出現對的按鈕。在「待審核」時你只能核准或退回,只有在「處理失敗」時,人工接管的按鈕才會浮現。

驗收總結:115 項測試全過的背後

在修正了票號遞增邏輯與序列化問題(解決了 Day 5 遺留的 Datetime/Enum 序列化教訓)後,我們終於迎來了全綠燈。

驗收項 結果 關鍵說明
快樂路徑端到端 ✅ 通過 進件至結案,確實記錄 4 筆狀態留痕
敏感攔截流程 ✅ 通過 觸發例外正確開票,案件停留在 Processing 等待人工
服務中斷接管 ✅ 通過 模擬服務崩潰後,由 HUMAN 成功接管並留痕
票號唯一性 ✅ 通過 多次開票序號正確遞增,不再有無聲湮滅現象
持久化往返 ✅ 通過 解決 Enum 與 Datetime 的序列化 Roundtrip 難題
狀態機跳關防禦 ✅ 通過 任何試圖繞過流程的操作皆觸發 TransitionError

結語:是「系統」還是「幾個 Demo」?

今日,零件組完了,畫面也能動了。115 個 Passed 標誌著流程編排區 v0.1 的誕生。

但作為架構師,我們必須誠實面對當前的限制:目前的系統是一個單人、單執行緒的 PoC。我們沒有真正的使用者權限控管(名字是自填的),也沒有處理多人在線的併發衝突。

這引發了一個值得深思的問題:一個「能跑通的程式碼」與一個「強韌的系統」之間,究竟還有多遠的距離?

明日,我們將迎來最終的端到端驗收。當我們導入真實的模型呼叫,面對不可控的輸入與輸出的洪流時,這套精心設計的架構是會展現其應有的強韌,還是會像紙糊的房子一樣倒塌?


上一篇
當 AI 罷工時:為什麼「焊死」後門才是負責任的設計?
下一篇
你的 AI 專案只是在跑 Demo,還是真正的「系統」?端到端驗收的 3 個殘酷真相
系列文
從 AI 助理到營運中台:金融 PM 的 30 天 Claude Code 治理實戰 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言