Skip to Content
核心概念Action policy

Action policy

Action policy 是 Custom Tables 的能力 ct.action_policy(major 1)。它把「誰可以改這份資料、走哪條路徑」從一般 grant 移到三個放在資料表與其 Command 上的控制項:

控制項位置回答
write_policy資料表哪一條路徑可以寫入它的資料列:any(預設)或 commands_only
execute_policy、execute grant 與 authorityCommand誰可以執行這個 Command,以及它用誰的資料表權限執行
exclude_requesterrequire_approval 規則,或 Command definition送出變更的人可不可以核准它

合起來,它們讓一張資料表只能由「指定群組不必持有任何資料表 grant 就能執行」的 Command 來改,並由另一個人核准。三者都不是不可變性:資料表 moderator 可以(透過 REST 或 IaC 的 table 行)把 write_policy 切回 any,而有稽核的例外——整表生命週期、維運操作與 schema migration——仍會改寫資料列(見它不做什麼)。

write_policy

settings.write_policy 是 any 或 commands_only。沒有這個 key 的資料表是 any。

  • any——每條寫入路徑照舊運作。
  • commands_only——任何主體所做、且不是已授權 Command action 的每一次資料列層級寫入,都會在任何東西被暫存、排入佇列或 commit 之前被拒絕。讀取、搜尋、匯出與分析完全不受影響,Command 仍然可以寫入。

設定方式

位置方式
建立資料表任一 scope 的建立 body 帶 settings.write_policy。其他值是 400(settings.write_policy must be one of ['any', 'commands_only'], got '<value>')。
既有資料表,走 RESTPATCH …/tables/{table_id}/write-policy,body 為 {"write_policy": "commands_only"}。需要 moderator 權限(與 default-permissions 相同的 gate)。
IaCtable 行的 spec.settings.write_policy;其他值是 parse 階段的 400。REST 的 table update 不能改 settings,所以這條路與 PATCH route 是切換既有資料表的僅有方式。
curl -X PATCH \ "$BASE_URL/private/module/custom_tables/department/33333333-3333-4333-8333-333333333333/tables/22222222-2222-4222-8222-222222222222/write-policy" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"write_policy": "commands_only"}'
{ "table_id": "22222222-2222-4222-8222-222222222222", "write_policy": "commands_only" }

這個 route 存在於三種 scope(chatroom/{chatroom_id}、department/{department_id}、company),資料表不存在以及已在垃圾桶中時回 404 CustomTable Not found(moderator gate 會先查這張資料表);在聊天室 route 上,資料表不在路徑的聊天室內時也是同一個回應;部門與公司 route 對 scope 外的資料表回 404 Table not found,任何 route 在查找之後資料表才被丟進垃圾桶時也回 404 Table not found:垃圾桶中資料表的 policy 無法更改,最後這項檢查是在鎖定的資料列上進行,所以並行的 trash 無法繞過。這個變更在資料表的列鎖之下只合併單一 key,所以會與每個寫入者序列化:在切換之前就開始的寫入會在 flush 時再檢查一次,無法寫進已變成 commands_only 的資料表。這個值會以 settings.write_policy 出現在每一個資料表回應中。

被拒絕的寫入長什麼樣子

每一種拒絕都帶同一個 typed 錯誤 governed_context_required:

{ "detail": { "error": "governed_context_required", "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.", "table_id": "22222222-2222-4222-8222-222222222222", "operation": "update" } }

這是 403,而且在任何副作用之前提出:沒有東西被暫存、沒有計數器或版本變動、沒有寫入 history、沒有發出確認 token,也沒有建立 Review process、durable receipt 或 idempotency marker。operation 指出寫入類型(create、update、delete、restore、revert、reorder、bulk、publish),surface 則在寫入來自某條具名通道時出現。各條通道如何回報:

寫入路徑拒絕方式
單筆 REST 的 create、update、delete;從垃圾桶 restore;history revert;測試資料帶上述 body 的 403(operation;產生測試資料列時 surface 為 test_data)
同步 bulk 與批次 action403,operation 為 bulk,在審批預掃之前
非同步 bulk 與匯入的送出在送出時回 403,早於 Redis 保留與排入佇列(surface 為 bulk_insert、bulk_update 或 bulk_delete);worker 會再檢查一次,ticket 以 error_code 帶出
Publish session開啟 session 時回 403(operation 為 publish、surface 為 publish_session);commit worker 會再檢查一次
重新排序403,operation 為 reorder,在鎖定的資料表列上檢查
外部 callback一般 token 檢查之後回 403(無效 token 仍維持統一的 404),早於 durable receipt 與 idempotency marker,所以沒有東西被保留,也不會把已儲存的 receipt 重播給資料表現在拒絕的寫入(surface 為 callback)
Trigger 的資料列 action(create_record、update_record、delete_record)與單筆 REST 寫入相同的 CRUD guard 會在寫入任何東西之前拒絕它(operation 為 create、update 或 delete),action 以該錯誤失敗
IaC record 行plan 時是 validate 階段錯誤、帶 code: "governed_context_required",所以 plan 不可 apply;若 apply 與切換賽跑,該行會回報 record '<table>' line <n>: governed_context_required — table accepts record writes only through Commands (write_policy: commands_only),而且沒有 code(見錯誤目錄)
Agent 資料列工具工具結果是 {"error": "governed_context_required", "message": "…", "table_id": "…"},早於任何 proposal、確認或 receipt
MCP 資料列工具(records_create、records_update、records_upsert、records_delete、records_restore、records_bulk、records_bulk_import_async、attachments_upload)在 dry_run 就回 governed_context_required: <message>,所以不會為一個 commit 時會被拒絕的寫入發出確認 token
Agent workspace 的 ct.write錯誤碼 governed_context_required
Sandbox 寫回以這種資料表為目標的寫回設定在儲存時就被拒絕(422 output_policy_governed_table);切換之前鑄造的 token,在 callback 時被拒絕

仍然可以寫入的

Command action。Command 執行器一次只為一個 action 進入 governed context——一張資料表、一種操作(insert、update 或 delete)——而且是在該 action 自己的授權通過之後,包括審批通過的計畫被 release 時。這個 context 絕不會由 request 參數、trigger 鏈或 callback token 設定,並且在每個離開路徑都會還原,所以失敗或巢狀的 action 不會把它留下。在 policy 切換之前就被擱置等待審批的直接(非 Command)寫入也不是例外:它在 release 時會被資料表的寫入底線(write floor)拒絕,該暫存變更以 apply_failed 結束,錯誤為 refused by the table's write floor (governed_context_required): …。被擱置的批次會在 results 逐列記錄該文字,並以 apply_failed 結束,錯誤為 N/M rows failed。審批通過的 Command 計畫仍會套用,因為它的 release 會重新進入 governed context。Command 沒有 restore 或 revert 這種操作,所以這類寫入在 commands_only 資料表上永遠被拒絕。

寫入該資料表的 Command 仍然需要有人能執行它。預設那表示對方有該 Command 各 step 所需的資料表權限;若要讓人在不持有這些權限的情況下也能執行 Command,見執行權限。

例外

只有三種變更不受管制。它們都已公開(OpenAPI、MCP instructions、agent cheat sheet 與能力探索),而且在 commands_only 資料表上,每一次例外操作都會寫一筆稽核事件:

例外 id涵蓋
table_lifecycle整張資料表的 trash、restore 與 purge。Purge 也會移除其他資料表指向被 purge 資料表的 link cell;每張因此失去 link cell 的存活 commands_only 資料表都會稽核為 table_purge_link_strip
platform_operator_maintenanceroot 與平台操作人員的端點:舊版 migration 匯入、sort-order backfill 這類 hotfix 修復,以及公司刪除
schema_migration因欄位新增、移除與型別變更、選項改名與 decimal 遷移造成的資料列 cell 改寫(它們也會以 schema_* 資料列 history 記錄)

稽核事件是名為 ct.write_policy.exemption 的結構化操作 log,帶資料表、公司、部門、聊天室、例外 id、操作(table_trash、table_restore、table_purge、table_purge_link_strip、column_add、column_remove、column_update、root_migration_import、root_sort_order_backfill、root_company_delete)與操作者。table_lifecycle 與 schema_migration 在改寫 commit 之後才送出一次,所依據的 policy 值是在該變更自己的 lock 之下讀到的:被拒絕或被回滾的操作絕不會記錄,跨越 policy 切換的 migration 則從第一個已 commit 的批次起就被稽核。維運操作則是在操作路徑碰到該資料表時才記錄:公司刪除在刪除任何東西之前、migration import 每個 flush 的批次一次、sort-order 回填在批次 commit 之後。稽核絕不會阻擋維運操作。

較舊的 agent 專用旗標

settings.agent_writes_via_commands_only 是另一個較舊的 opt-in,只會把 agent 的四個資料列寫入工具導向 Command,而且只在房間的 roster 中有可用 Command 時才生效。write_policy: commands_only 是全域的:不論 roster 為何,它都拒絕上表每一條通道,即使看不到任何 Command,agent 的拒絕也會觸發。兩者可以同時設定,但只要 roster 中有可用的 Command,較舊的旗標會先回應,回 table_governed_by_commands;當你指的是資料表時,請單獨使用 write_policy。

它不做什麼

  • 它不是不可變性。 資料表 moderator 可以把 policy 切回 any,manager 可以撰寫或更改寫入該資料表的 Command。
  • 它不替 Command 授權。 Command 仍然需要它自己的執行權限、資料表權限或 definer ceiling,也仍然要通過資料表的規則、ACL 與審批。
  • 操作人員屬於例外,在上述三個 id 之下以稽核事件取代拒絕。
  • Schema 變更不是這個 policy 的寫入。 新增欄位或變更欄位型別,是在 schema_migration 例外下改寫 cell。

能力探索

GET …/core/capabilities 會列出 ct.action_policy,major 為 1。它是唯一帶有選填 exemptions 欄位的能力,其值是上述例外 id 的陣列;其他每個能力都不會有這個 key。

{ "id": "ct.action_policy", "major": 1, "available": true, "reason": null, "exemptions": ["table_lifecycle", "platform_operator_maintenance", "schema_migration"] }

探索不授予任何資料存取。這個能力涵蓋 write_policy、Command 的 execute_policy 與 execute grant、authority: definition 與它的 table_access ceiling,以及 exclude_requester;build 是否可用是來源事實,不代表任何環境已部署它。

延伸閱讀

Last updated on