
Day 11 把一個登入問題一路追到 Frontend state mapping,先回答了:
這次該改到哪裡?
但 Scope 正確,不代表功能就一定做對。
假設需求只有一句:
讓 ProxyAdmin 也可以幫別人點餐。
這句話放進真實系統後,立刻會多出一串產品決策:
如果這些問題沒有先回答,AI 還是可以很快做出一個「合理版本」。
只是合理,不等於符合這套系統既有的業務規則。
Acceptance Criteria 的價值,不是把需求寫得更正式,而是先決定什麼叫做做對。
Day 2 其實已經出現過 Acceptance Criteria。當時它主要用來從 Legacy 畫面反推需求;走到今天,角色、日期、截止時間與餘額都已經互相牽動,AC 開始多一個責任:在 AI 動手前,把行為契約固定下來。
Delegated ordering 最容易被誤解成:
多一個 target user 就好了。
畫面上可能真的只是多一個成員選擇器,但 Domain 至少同時在回答:
誰可以代點
+
哪一天可以代點
+
操作後誰的狀態被改變
這三件事如果各自由不同層自己猜,很容易得到一個很麻煩的結果:
每一段程式單獨看都合理,合起來卻不是同一套規則。
這次 repo 裡的測試就把角色差異寫得很具體。
ProxyAdmin:
Admin 則不同:對有效訂餐日期具備更高的操作能力,測試也明確涵蓋 cutoff 之後的 delegated ordering。
這已經不是一個按鈕要不要顯示的問題。
它是角色、日期、Calendar 與 Deadline 共同形成的規則。
這次不是先寫完功能,再回頭想「要測什麼」。
Repository 裡對 delegated ordering 已經有幾個很具體的判定:
User 偽造 targetUserId
→ 403 DELEGATED_ORDER_FORBIDDEN
ProxyAdmin 代點今天、cutoff 前
→ 200
ProxyAdmin 代點今天、cutoff 後
→ 400 DEADLINE_CLOSED
ProxyAdmin 代點未來有效日期、該日期 cutoff 前
→ 200
ProxyAdmin 代點未來日期,但該 calendar group 未開
→ reject
Admin delegated ordering
→ 擁有較高日期/cutoff authority
這組測試有一個很重要的特性:它沒有只證明「ProxyAdmin 可以代點」。
它同時把誰、哪一天、什麼狀態、什麼例外固定下來。
而且 Worker 端的 authorization contract 仍然是最後權威:ProxyAdmin 只能擴大 delegated ordering 的 eligible date range,不會因此繞過 canonical calendar/menu cutoff;真正具備 elevated cutoff bypass 的仍是 Admin。
這也讓 AC 不只是產品文字,而是能一路追到可重跑的 PASS / FAIL。
如果哪天 UI 又把未開團日期顯示成可操作,或某次 refactor 不小心讓 ProxyAdmin 越過 cutoff,測試不是在檢查「畫面有沒有照原本寫」,而是在提醒:
這次實作已經偏離當初確認過的 Business Rule。
需求可以先改寫成能直接判斷 PASS / FAIL 的句子。
例如 ProxyAdmin 的允許案例:
Given 使用者具備 ProxyAdmin 權限
And 指定日期是今天或未來
And 該日期已開團
And 尚未超過該日期 cutoff
When 替另一位可操作成員建立訂單
Then 系統接受操作
And 訂單歸屬於被代點成員
禁止案例則可以分開寫:
Given 使用者具備 ProxyAdmin 權限
And 指定日期是過去日期
When 嘗試建立 delegated order
Then 系統拒絕操作
And 不產生訂單
And 不改變任何餘額
以及:
Given 使用者具備 ProxyAdmin 權限
And 指定日期尚未開團
When 嘗試建立 delegated order
Then 系統拒絕操作
這裡刻意沒有寫:
因為 AC 描述的是外部可觀察的行為契約,不是 implementation plan。
只要這份 observable behavior 沒變,底層未來仍有重構空間。反過來,如果 AC 一開始就綁死檔案與函式,測試很容易只是在保護目前寫法,而不是保護 Business Rule。
AI 很擅長先把主要路徑打通。
真實系統比較容易踩坑的,反而是邊界。
| 類型 | 代點餐例子 |
|---|---|
| Happy path | ProxyAdmin、今天/未來、已開團、cutoff 前 |
| Boundary | Admin 與 ProxyAdmin 對 cutoff 的權限不同 |
| Forbidden | 過去日期、未開團、角色不符 |
| State consistency | 訂單與餘額必須落在 target user,而不是 actor |
這些條件在系統裡不是抽象規則。實際代點時,角色、目前代點對象、目標餘額與每一天能不能預訂,都會一起出現在操作畫面上。

如果只驗 Happy path,很容易得到「功能已完成」的錯覺。
把規則寫清楚的,通常是第二、第三列。
測試裡還有一個很容易被忽略的邊界:
ProxyAdmin 即使能查看某個 target 的歷史 context,也不代表該日期允許替他下單。
可查看
≠
可下單
這種差異如果沒有先進 AC,UI 只要已經拿得到 target context,就很容易讓「看得到」被誤解成「可以操作」。
兩個功能表面都和「切換成別人」有關。
但語意完全不同。
View-As 的核心是:
用另一個人的視角查看系統
Delegated ordering 的核心則是:
由具備權限的人
代表另一個人
產生一筆會改變狀態的操作
前者偏向 read context。
後者會建立訂單、改變 target balance,還需要留下 actor / target 的責任關係。
如果只從 UI 長相出發,因為兩邊都有 member selector 就共用同一套權限,很容易把「看得到」和「可以代替他改資料」混成同一件事。
AC 的作用,就是在實作之前先把這種語意差異攤出來。
這也是 Vibe Coding 進入真實系統後,我越來越在意的一個落差。
早期功能單純時,一句自然語言需求常常就能得到很接近預期的結果。
系統變大後,同一句話背後可能已經綁著:
AI 可以幫忙把模糊需求整理成 AC,但最後仍需要人確認:
產碼速度可以交給 AI,加快產品決策並不代表產品決策本身也能外包。
第一輪是用案例類型找洞:
Happy path
Boundary
Forbidden
State consistency
它回答:
我是不是只想到「成功時會怎樣」?
第二輪再把需求壓成四個最小問題:
Who
誰可以做?
When
什麼日期/狀態下可以做?
Effect
成功後誰的哪些狀態必須改變?
Reject
哪些情況必須完全不產生副作用?

兩套看起來都像四格,但用途不同。
前一套用來找漏掉的案例;後一套用來寫最小行為契約。
這樣一句「讓 ProxyAdmin 也可以幫別人點餐」,才會從一個自然語言願望,變成 AI、測試與人都能判斷 PASS / FAIL 的工作。
下一步才是另一個問題:
當一條 AC 真的穿過 Frontend、API、Auth、Domain 與 Persistence 時,要在哪些地方看到證據,才算整條鏈都改對?
這不是純概念示範,而是一套持續開發中的便當訂購系統。文章著重「為什麼」,GitHub 保留實際程式碼、測試、文件與演進紀錄。
GitHub:https://github.com/henryfir456/bento-order-app