前言

上一篇 讓 Codex CLI 與 OpenAI API 共用同一套分析 contract。但 output 就算通過 strict schema,也只代表資料格式正確,不代表組織已同意範圍、點數或財務影響。

內部估點系統因此把 suggestion、approval、hold 與 settlement 拆成不同狀態與 ledger entries。AI 可以縮短整理時間,卻不能自己扮演核准人。

原本的做法與問題症狀

第一版最省步驟的流程是:

AI suggested points
  → 直接寫入 request
  → 扣除可用點數

這會失去幾個重要答案:

  • 建議值和核准值是否不同?
  • 誰確認需求已完整?
  • 預扣和最終扣款是否相同?
  • 範圍縮小時如何釋放差額?
  • 餘額不足時誰授權超額?
  • 完工後哪些修正應回饋給下一次估點?

另一個問題是只保存 request 的最終數字。覆寫欄位看得到現在,卻看不到事件順序;遇到重算、範圍調整或爭議時,無法還原 hold、release 與 charge。

問題是怎麼推敲出來的?

Estimate runner 成功後把 request 設成 PENDING_APPROVAL。Approval service 先驗證點數單位與 status transition,才產生 approved draft;Ledger service 再根據角色與餘額建立 entries。

來源的 domain rules 明確拒絕:

  • Engineer 核准或預扣。
  • Engineer 執行結算。
  • PM 在餘額不足時自行超額預扣。
  • PM 在結算金額超過釋放後可用餘額時自行放行。

超額時只有 Admin 可以繼續,且必須保存 approver、reason 與 follow-up status。這讓「按下核准」和「產生財務影響」之間有可測試的規則,而不是 UI 按鈕顏色。

最後採用的架構

AI Analysis
  → PENDING_APPROVAL

PM review/edit
  → APPROVED
  → HOLD

Execution
  → progress/actual hours

PM settlement
  → RELEASE 原預扣
  → optional OVERDRAFT_APPROVAL
  → CHARGE 最終點數
  → HistoricalCase

資料也分成三類:

資料 目的
AiEstimateAnalysisRun 保存模型建議與輸入輸出證據
WorkRequest approval fields 保存人工核准狀態、數值、時間與說明
Point ledger entries 保存 hold、release、charge 與超額授權事件

AI output 不直接建立 ledger entry;只有通過 application workflow 的人工 action 才能造成預扣或結算。

關鍵實作

預扣會檢查角色與餘額:

if (actorRole === 'ENGINEER') {
  throw new Error('Only PM or ADMIN can confirm pre-hold');
}

if (availablePoints < points && !overdraftApproval) {
  throw new Error('Insufficient available points');
}

return [{type: 'HOLD', points}];

結算不是把 HOLD 改名,而是追加事件:

const entries = [
  {type: 'RELEASE', points: heldPoints},
];

if (needsOverdraft) {
  entries.push({type: 'OVERDRAFT_APPROVAL', points: overdraftPoints});
}

entries.push({type: 'CHARGE', points: finalChargePoints});

這樣可以同時保存「原本預扣多少」與「最後實際扣多少」。Settlement 還會收集 actual hours、PM adjustment reason、engineer notes 與 case quality,供完成案件回饋。

實際驗證

來源測試可證明:

  • AI estimate 成功後狀態是 PENDING_APPROVAL
  • Engineer 無法 approve、hold 或 settle。
  • PM 在餘額足夠時可建立 HOLD。
  • 一般 PM 在餘額不足時會被拒絕。
  • Admin 超額預扣或超額結算必須帶 audit metadata。
  • Settlement 依序建立 RELEASE、選擇性的 OVERDRAFT_APPROVAL 與 CHARGE。
  • Invalid status transition 會被拒絕。
  • Settlement draft 保存 AI 建議、核准預扣、最終扣款、實際工時、調整原因與工程備註。

這些驗證在 service/domain tests 層成立;本次沒有進行多帳號 browser test、database concurrency test 或正式財務 reconciliation。

仍然存在的限制

最重要的限制是:來源 README 明確指出目前只有單一管理員登入 gate。雖然 domain 層已有 PM/Admin/Engineer 規則與 permission foundation,現有 web flow 還不能證明完整的多使用者 identity-bound RBAC。

換句話說,程式已建立「角色應有什麼權限」與「哪些 transition 要阻擋」的基礎,但尚未完成每位操作者的獨立帳號、角色綁定、session actor、separation of duties 與 end-to-end authorization test。這不能被包裝成已完成企業級權限系統。

第二,Application ledger 是可追溯事件紀錄,不是不可變更的會計總帳。還需要 database constraints、idempotency key、concurrent action protection、append-only policy 與 reconciliation job。

第三,Approval note 與超額理由要避免放入不必要的個資或附件原文。稽核需要證據,不代表無限制保存所有資料。

第四,AI 建議與人工核准值的差異目前有欄位可保存,卻沒有自動產生 accuracy/bias/drift 指標。沒有量測,就不能證明系統愈用愈準。

可以延伸到哪些情境?

同樣的拆分可以用在折扣發布、內容審核、資料刪除、採購與權限變更:

AI proposal
  ≠ human decision
  ≠ side effect
  ≠ final settlement

每一步都要有 actor、input、狀態、時間與可回復方式。OpenAI 的現行 safety guidance 也建議輸出在投入實務前由人類審查,並讓 reviewer 能回看原始資料;這正是 approval 不應只剩一個按鈕的原因。

下一篇追蹤結算後的資料如何回到下一次估點:如何把完成案件變成下一次估價可用的 RAG 知識庫?

參考資料

以上平台文件查核日期:2026-07-31。