建立自訂資料表核准流程
本指南把 require_approval、Review 模組與 staged changes 串成一條可運作的流程。關鍵觀念是:符合規則的 write 會先建立 immutable intent 與 review process;只有最終核准後,worker 才用真正 CRUD 路徑套用,當時有效的 rules、ACL 與 invariant 仍會再判斷。
1. 設定 Review 模組管理權
Company manager 先指定一位 Review manager。Target 可為 user 或 department,兩者只能擇一:
curl -X POST "$BASE_URL/private/module/review/access" \
-H "Authorization: Bearer $COMPANY_MANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user_id": "11111111-1111-4111-8111-111111111111",
"role_type": "manager"
}'Review manager 可管理 groups 與 templates;一般 reviewer 不必有 module grant,pending assignment 本身就是讀取 process 與投票的授權。詳見 review.access.grant。
2. 建立 reviewer group
curl -X POST "$BASE_URL/private/module/review/groups" \
-H "Authorization: Bearer $REVIEW_MANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "高額訂單覆核人員",
"description": "負責核准高額訂單變更",
"member_ids": ["22222222-2222-4222-8222-222222222222"]
}'保存回傳的 group_id。Group members 必須是同公司 users;後續修改 group 不會偷偷改寫已建立 process 的歷史 assignments。端點見 review.groups.create。
3. 建立 Review template
curl -X POST "$BASE_URL/private/module/review/templates" \
-H "Authorization: Bearer $REVIEW_MANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "高額訂單覆核",
"description": "單一主管關卡",
"require_signature": false,
"gates": [
{
"name": "主管覆核",
"condition": {
"type": "group",
"group_id": "33333333-3333-4333-8333-333333333333",
"mode": "any"
}
}
]
}'保存 template_id。Table rule 只能引用同公司、未刪除且 gate groups 仍有效的 template。Template 更新只影響之後建立的 process。端點見 review.templates.create。
Gate 的 condition 不限於 group。{"type": "user", "user_id": "<36 字元 uuid>"} 這種 leaf 直接指名一位核准人,所以「只要一個人簽」的關卡不必再為它建一個只有一人的群組;這種 leaf 也可以和 group leaf 一起掛在 and/or 之下自由組合。被指名的人必須是同公司的活躍使用者,否則 template 儲存時就是 422 review users not found or inactive: [...]——在前面擋掉,而不是生出一個沒有人能決定的流程。condition 文法、各項上限,以及產生的 assignment 上 group_ids 長什麼樣,見覆核模組管理。
4. 加入 require_approval rule
Table moderator 先 GET 完整 rules、加入候選規則、preview,再整份 PUT:
{
"type": "require_approval",
"name": "高額訂單需核准",
"label": "高額訂單變更",
"events": ["create", "update", "delete", "restore"],
"template_id": "44444444-4444-4444-8444-444444444444",
"when": [
{ "column": "金額", "op": "gte", "value": 100000 }
]
}每張表最多一條 approval rule。有 pending staged changes 時不能修改或移除它;create/update trigger actions 也不能指向這張表。完整欄位見 require_approval 與 rules.set。
5. Writer 送出變更
Writer 照常呼叫 record create/update/delete/restore。若規則命中,private API 回 409:
{
"detail": {
"error": "approval_required",
"process_id": "55555555-5555-4555-8555-555555555555",
"rule_id": "rule_a1b2c3d4",
"rule_label": "高額訂單變更",
"staged_change_id": "66666666-6666-4666-8666-666666666666"
}
}把這個 response 當作「已送審」而非一般失敗。保存兩個 IDs、停止自動 retry,並顯示 pending 狀態。同一目標已 pending 時是 approval_pending;每位 actor 在每張表的 500 筆 pending bucket,或整張表的 50 筆 pending create bucket 滿時,會回 structured 429 staged_cap_exceeded。前端應直接用 response 的 detail.scope 與 detail.limit 顯示佇列已滿:scope: "actor", limit: 500 是 actor/table 額度,scope: "table", limit: 50 是 pending-create 額度;不要把數字寫死,也不要從本次操作自行猜 scope。
自動建立的 Review process 已針對審核中心在地化。Title 為 表格「<table name>」<operation label>審核,例如 表格「訂單」修改資料審核;content 直接帶出渲染 held change 所需的 context,不必再逐筆查 table/scope:
{
"record_id": "33333333-3333-4333-8333-333333333333",
"change_type": "update",
"change_type_label": "修改資料",
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"scope": "chatroom",
"scope_label": "聊天室",
"scope_id": "11111111-1111-4111-8111-111111111111",
"scope_name": "訂單客服"
},
"requester": {
"type": "user",
"id": "44444444-4444-4444-8444-444444444444",
"name": "王小明",
"channel_label": "內部使用者",
"department_id": "55555555-5555-4555-8555-555555555555",
"department_name": "財務部"
},
"diff": { "金額": { "old": 1000, "new": 1200 } }
}若寫入來自 external client,content.requester 改為帶 type: "external_client"、id、name、platform、在地化 channel_label、chatroom_id 與 chatroom_name。名稱 enrichment 由 tenant 錨定並 fail-soft。這個 content-level requester 描述「誰造成 custom-table change」;它不同於 process list 頂層的 requester,後者描述 generic Review submission 的人類送審者。
Moderator 可用 stagedChanges.list 依 pending / applying / applied / apply_failed / discarded 追蹤 table-side 狀態。
一批就是一次覆核,不是每個動作一次。 當規則命中同步的 POST .../records/bulk——或與它對應的 AI 工具 custom_tables_bulk_record_actions——gate 會在動作迴圈之前先跑,並只暫存一筆 change_type: "batch_actions" 的 staged change 與一個 Review process,涵蓋整批動作。沒有任何一半會先執行,而覆核者的一個決定就放行或作廢整批。不要預期每一列都有一筆 staged change,也不要在整批 pending 時單獨重送其中的動作。
在 AI 這一側,這些結果是終局的,不是暫時性的。 佇列滿的拒絕以前是最大的陷阱:staged_cap_exceeded 是 HTTP 429,落在工具箱只解讀 400/409/422 的範圍之外,傳到 AI 手上變成通用的「發生內部錯誤」。於是它不斷重試一個在簽核人清空佇列前不可能成功的寫入,而使用者完全沒被告知這跟核准有關。現在 AI 拿到的是帶著上限值的政策訊息——簽核佇列已滿:這張表待審核的變更已達上限(<limit>)…——與「變更已送審」「該記錄已有變更在審」並列,成為它在同一輪內不該重試的三種結果。詳見 Agent 工具箱。
6. Reviewer 作 assignment decision
Reviewer 先列出只屬於自己的 pending assignments:
curl "$BASE_URL/private/module/review/assignments/pending?skip=0&limit=50" \
-H "Authorization: Bearer $REVIEWER_TOKEN"再對一筆 assignment 投票:
curl -X POST \
"$BASE_URL/private/module/review/assignments/77777777-7777-4777-8777-777777777777/decision" \
-H "Authorization: Bearer $REVIEWER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "decision": "approved", "comment": "金額與附件已核對" }'拒絕時使用 "decision": "denied",且 comment 必須非空。Template 若要求 signature,先上傳 reviewer 自己的 image blob,再帶 signature_blob_id。Ballot 已決定時重送會 409。參考 review.assignments.pending 與 review.assignments.decision。
7. Release 或 discard
最後一個 gate 核准後,Review process 變成 approved,內部 consumer 會 claim staged change 並以真實 CRUD 套用:
pending → applying → applied
↘ apply_failed此時不會沿用送審當下的「已通過」假設;若 record version、unique key、ACL posture、channel scope 或 invariant 已變更,套用可進入 apply_failed。產品應同時觀察 Review process 與 staged-change status,不能只看到 approved 就立即顯示資料已落地。
任何 gate denied 或 process cancelled 時,變更不落地並進入 discard 路徑。Table moderator 也可對不再需要或需人工清理的 IDs 呼叫 stagedChanges.discard;回應是逐 ID outcome,請逐筆檢查,不要只看 top-level HTTP 200。
若 approved apply 因 queue 交付卡住,平台 operator 使用 staged-change requeue,而不是替 reviewer 再投一次票。
要在不手寫整套導覽 UI 的情況下練習這條流程,最後請開啟流程精靈。