前言
上一篇 讓 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
資料也分成三類:
| 資料 | 目的 |
|---|---|
AiEstimate/AnalysisRun |
保存模型建議與輸入輸出證據 |
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 知識庫?
參考資料
- OpenAI:Safety best practices
- OpenAI:Structured model outputs
- OpenAI:Data controls
- Shopify:Work with protected customer data
以上平台文件查核日期:2026-07-31。
