Skip to Content

執行權限

預設情況下,任何能讀取 Command 所引用的每一張資料表、並通過它的 visibility 的人都能執行它,而它讀寫時使用的是呼叫者自己的資料表權限。action policy 的一部分同時改變這兩半,讓一張資料表可以透過一個 Command 來改,而執行的人不需要持有資料表上的任何 grant:

問題控制項預設
誰可以執行?execute_policy 與 execute grantopen
它用誰的資料表權限?definition.authority 與它的 table_access ceilinginvoker
呼叫者拿回什麼?status-only 結果,definer authority 下自動啟用完整

誰能執行 Command

execute_policy 設定在 Command 的建立與更新 body 上(REST 與 IaC):

  • open(建立時省略的預設值)維持既有規則:讀取每一張被引用的資料表,並通過 visibility。
  • restricted 只接納每一張被引用資料表的現任 manager,以及持有有效 execute grant 的主體。資料表讀取權限打不開它、沒有建立者後門,而且移除最後一個 grant 也絕不會重新打開它。visibility 不會被參考,所以 private 的受限 Command 對每一位 grantee 都會列出、也都能執行。Manager 身分是依呼叫者自己的 user id 判定:client principal(外部或 Agent 平台)永遠不算 manager,即使它所委派的人類管理每一張被引用的資料表也一樣,所以它需要 grant,而且從 definer Command 得到的永遠是 status-only 結果。

REST PUT 時省略 execute_policy 會保留目前的值,所以整份覆寫的更新絕不會悄悄重新打開受限 Command;IaC 的 command 行則相反,省略這個 key 代表 open(見以 IaC 撰寫)。明確設成 open 在兩條通道上都會重新打開,除非 definition 宣告了 authority: definition,它要求 restricted(REST 為 422 definition_requires_restricted,IaC 行則是 parse 階段的 400)。

這道 gate 在每一條會執行 Command 的通道上都即時檢查——REST execute、agent、MCP、trigger 的 invoke_command 與審批 release——所以撤銷 grant 或 manager 角色立即生效,連已經暫存的計畫也一樣。閘外的呼叫者會得到帶 program-error 信封的 403,error: "command_execute_denied"(This Command is restricted and the caller holds no execute grant、phase: "execute"、details: {};MCP 工具改回 not_found)。受限 Command 對不能執行它的呼叫者也是隱藏的:它不會出現在列表中、讀取時回一般的 Command 404,也不在 agent 的 roster 內。

Execute grant

一個 grant 指名一個可以執行一個受限 Command 的主體。Grant 只在 Command 為 restricted 時才有意義。

Route用途
GET …/commands/{command_id}/grants該 Command 的 grant {items, total}
POST …/commands/{command_id}/grants授予一個主體。具冪等性:已存在的 grant 會原樣回傳
DELETE …/commands/{command_id}/grants/{grant_id}撤銷,立即生效;回傳被刪除的 grant

這些 route 存在於三種 scope({scopeWithId}),而且每一個都要求即時管理該 Command 引用的每一張資料表——與編輯它的規則相同,不看 created_by。

{ "principal_type": "department", "principal_id": "33333333-3333-4333-8333-333333333333" }
{ "id": "5d1c0f0e-8a0a-4a53-9a63-0c8d2a8c9a11", "command_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", "principal_type": "department", "principal_id": "33333333-3333-4333-8333-333333333333", "created_by": "99999999-9999-4999-8999-999999999999", "created_at": "2026-10-01T09:00:00" }

principal_id 最長 36 個字元,目標必須是該 Command 所屬公司中存活的使用者、部門或聊天室,或是房間屬於該公司的既有 client:其他一律是統一的 404 Grant principal not found,所以 grant 請求絕不能用來探測別家公司的 id。不存在的 grant id 是 404 Grant not found。Grant 是對實際主體比對的:

principal_type符合
user主體自己的使用者,或 Agent 平台 client 所委派的已驗證人類
department已驗證人類現在所屬的部門——絕不是發出呼叫的部門
chatroom屬於該存活聊天室、且為存活成員的已驗證人類。綁定房間的呼叫(agent;呼叫聊天室範圍 command 的 trigger)只計算 acting room 的 grant,而且仍需要存活的成員資格,因為非成員也可以為某個房間鑄造出 Agent 平台 client。REST 或 MCP 對聊天室範圍之 version-2 command 的呼叫,其審批 release 同樣綁定該 command 所屬的房間,所以在那裡只計算它自己房間的 grant
client外部 client id(外部社群媒體 client 需要明確的 grant;房間 grant 絕不會接納它)

已驗證人類是存活且不是服務帳號的使用者。對人而言,該 scope 自己的門檻仍然適用:REST、MCP 與 trigger 只讓聊天室範圍 command 所屬房間的存活成員執行它:透過 REST,非成員會得到房間門檻的 403 Insufficient permissions, not a member …,通過門檻的 manager 則得到 403 Command principal is not authorized;MCP 回 not_found;trigger run 則會失敗。綁定到該房間的 client 不檢查成員資格:外部 client 靠 client grant 執行受限 Command,為該房間鑄造的 Agent 平台 client 則靠 user、department 或 client grant 執行,即使它背後的人類不是成員;只有 chatroom grant 會檢查成員資格。

Execute-only 呼叫者看到什麼

可以執行受限 Command、但不管理它引用的每一張資料表的呼叫者,拿到的是介面,不是程式。GET 與列表回傳的 Command 是 definition: null、definition_redacted: true、lifecycle_redacted: true 與 interface: {inputs, outputs, mode}——宣告的 inputs、output 名稱與 mode("query",寫入型 command 則是 null),絕不含 steps、ceiling、rules、callbacks 或 triggers。Manager 看到的仍是完整的 definition。

定義者權限

definition.authority: "definition" 讓 Command 用它自己宣告的 ceiling 執行,而不是用呼叫者的 grant。它是為 execute-only Command 設計的:呼叫者不需要任何資料表權限,只需要執行它的權利。帶有這種 authority 的 Command 不能帶 attachment_copies grant:儲存時就會被拒絕,因為 grant 的來源 select 必須以呼叫者自己的讀取權執行。

{ "version": 2, "authority": "definition", "table_access": { "11111111-aaaa-4aaa-8aaa-111111111111": { "read": "all", "update": true }, "22222222-bbbb-4bbb-8bbb-222222222222": { "read": "filtered", "read_filter": { "column": "col_aaaaaaaa_aaaa_4aaa_8aaa_aaaaaaaaaaaa", "op": "eq", "value": "open" }, "insert": true } }, "inputs": [ … ], "steps": [ … ] }
規則契約
前提execute_policy 必須是 restricted(建立與 PUT 時為 422 definition_requires_restricted;/validate 不會跑這項檢查),而且 Command 必須是寫入型 command(authority `definition` is not supported for query-mode commands)
table_access從資料表 id 對應到 {read, read_filter, insert, update, delete} 的 map,最多 20 個項目。read 是 none(預設)、all 或 filtered,read_filter 是以內部 col_<hex> 欄位 key 為鍵的 row policy,恰在 read 為 filtered 時必填(儲存時只檢查它是否存在),insert、update 與 delete 預設為 false。REST 對格式錯誤的組合回 model 的訊息:authority `definition` requires a non-empty table_access、table_access is only allowed with authority `definition` 、table_access.read `filtered` requires read_filter 或 table_access.read_filter is only allowed with read `filtered`
必須與程式吻合它必須恰好指名 definition 引用的資料表(寫入目標、select 與 join 來源與相依資料表):透過 REST 時,缺少或多出的 id 在建立與更新是 422 definition_ceiling_invalid,帶 missing_table_ids 與 extra_table_ids;在 /validate 報告中則是 status: 422 的 errors[] 項目(HTTP 200、valid: false)。IaC apply 不會做這項檢查:缺少的項目要到 Command 執行時才以 403 definition_ceiling_missing 浮現,多出的項目雖然會被接受,但必須是同租戶、且 owner 有管理權的資料表,否則 Command 會以 403 definition_authority_invalid 失敗。作者本來就管理每一張被引用的資料表,所以指名這些 id 不會變成存在性 oracle
逐項操作read、insert、update 與 delete 各自獨立強制,兩種 definition 版本皆然
絕不是 managerceiling 取代呼叫者的 grant,但 definer 不是 manager:row filter 與資料表 column_acl 的隱藏欄位仍然生效,租戶永遠有約束力。綁定房間、在部門資料表上的呼叫,會再受 acting room 為該呼叫者 audience 所設的 grant 資料列限制(沒有資料列就什麼都沒有)
Owner伺服器把儲存這一版的 manager 記為 authority_owner_id;client 永遠不能指定,而且每次由另一位 manager 重新儲存都會重新推導

Owner 必須保持有效。在每一次執行、冪等 replay、trigger 執行與審批 release 時,伺服器都會檢查 owner 是存活、屬於該 Command 的公司、不是服務帳號、而且仍是每一張 table_access 資料表的 manager,並檢查每張被引用的資料表都有 ceiling 項目;否則呼叫會 fail closed,回 403 definition_authority_invalid(訊息會請一位管理每一張資料表的 manager 重新儲存這個 Command)或 403 definition_ceiling_missing。Owner 會隨每次執行一起記錄、並凍結進暫存的計畫,release 時若發現它變了,會以 source_authority_changed、field 為 authority_owner_id 結束(見審批)。

Status-only 結果

在 definer authority 之下,每一個呼叫者看得到的結果——即時 body、冪等 replay、錯誤——除非呼叫者管理每一張被引用的資料表,否則都是 status-only receipt:

{ "execution_id": "ffffffff-ffff-4fff-8fff-ffffffffffff", "status": "succeeded", "steps": [], "redacted": true, "executed_at": "2026-10-01T09:00:00Z", "outputs": {} }
  • Receipt 不帶資料列、outputs、result ref 或 response body,唯獨作者選擇公開的公開結果除外。executed_at 仍保留(冪等 key 的 replay 只顯示原本的 execution_id 與 executed_at),完整 body 有 outputs 而沒有任何公開項目時 outputs 是 {},effect-identity 的 replay 仍標示 "replayed": true。
  • 不在一份固定清單上的每個錯誤——包括純字串 detail,以及凍結版本之前、指令資料列已不存在時所拋出的錯誤(REST /execute 對未知的 id 就回 404 加這個 body)——都會換成 {"error": "command_failed", "message": "The command could not be completed.", "redacted": true},狀態碼維持原樣。清單上的 25 個錯誤碼會原樣通過,只保留 error、phase、message、retryable、details、process_id、rule_id、rule_label、staged_change_id、table_id、operation 與 surface,所以沒有 step 與 path:approval_required、approval_pending、staged_cap_exceeded、governed_context_required、command_execute_denied、definition_authority_invalid、definition_ceiling_missing、source_authority_changed、schema_dependency_changed、source_version_changed、source_link_changed、frozen_manifest_changed、idempotency_key_reuse、idempotency_in_progress、idempotency_context_conflict、command_lock_conflict、command_contract_mismatch、query_command_requires_query_route、command_timeout、no_eligible_approver、requester_unattributable、invalid_agent_group_scope、ct.effect_identity_conflict、ct.effect_identity_replay_forbidden 與 effect_identity_unbound。其他任何錯誤碼,例如 approval_template_unusable、multiple_approval_gated_tables 或 command_link_graph_changed,都會變成 command_failed,輸入驗證的 400/422、assertion_failed(失敗的 assert step 若是公開則例外)與 command_rule_failed 規則失敗,以及純字串的 Read/Insert/Edit access not granted 403 也是:execute-only 的呼叫者只會得知狀態碼。
  • 該 Command 的 dry run 適用同樣的規則。對這類呼叫者,它的成功 body 本來就是遮蔽後的 {"status": "preview", "outputs": {}, "redacted": true};自後端 5.22.0 起,它的錯誤也和 /execute 的錯誤一樣被取代:狀態碼相同、command_failed body 相同,會原樣通過的錯誤碼與公開的 assert 訊息也相同。5.22.0 之前,dry run 會把完整的引擎錯誤交給 execute-only 的呼叫者,包含錯誤碼、phase、step、path、details、assertion 訊息與輸入驗證文字。
  • 執行歷史(…/commands/{command_id}/executions)會把這種執行顯示為 redacted: true、steps: []、沒有 outputs(公開項目除外),錯誤文字是 Command execution failed;input snapshot 仍保留,完整紀錄則留在供 manager 查看的稽核資料列中。
  • 呼叫 definer Command 的 trigger run 失敗時,只會記錄不含值的錯誤碼,因為 trigger run 可由宿主資料表的 moderator 讀取,而他們不一定管理該 Command 的每一張資料表。
  • 投影的判斷依據是實際執行的那一版(replay 則依據原執行的不可變 snapshot)與呼叫者的即時權限,所以並行的重新儲存無法讓已產生的結果解除遮蔽。若無法做出判斷,就一律遮蔽。

所以 agent 回報的是 status-only receipt,不會承諾或重述其中的資料列內容,只會陳述公開 receipt所載的事實。

作者選擇公開的結果

自 backend 5.21.0 起,作者可以讓 execute-only 的呼叫者看到 definer 執行的特定事實,讓呼叫者、Agent 與下一輪對話知道發生了什麼,而不是只知道成功。這是逐項 opt-in:version-2 的純量 output(outputs.<name>)或 assert step 加上 "public": true。未設定時 key 會省略,true 是唯一接受的值。沒有這個旗標、呼叫者管理每一張被引用的資料表,或是 invoker-authority 的 Command,回應都不會有任何變化:旗標只在結果是 status-only 時才有作用。伺服器在儲存 Command 時仍會檢查每個公開項目,不論它的 authority 為何。在 invoker-authority 的 Command 上,依賴 relation 的公開項目(包含排在 select 或資料列寫入 step 之後的公開 assert)會以 command_public_value_unsafe 被拒絕,因為讀取 relation 需要 read: "all" 的 table_access 項目,而只有 definer 的 Command 才能有(見哪些可以公開)。

{ "outputs": { "note": { "value": "$input.note", "description": "The note you sent", "public": true }, "accepted": { "value": true, "description": "Whether the request was accepted", "public": true } } }
{ "kind": "assert", "name": "within_limit", "condition": { "$lte": ["$input.qty", 10] }, "message": "At most 10 per request.", "public": true }

公開 output 的 description 與公開 assert 的 message 都會顯示給呼叫者,請不要把機密放進去。

呼叫者看到什麼

成功的執行會回覆 status-only receipt,外加公開的值與 public_receipt:

{ "execution_id": "ffffffff-ffff-4fff-8fff-ffffffffffff", "status": "succeeded", "steps": [], "redacted": true, "executed_at": "2026-10-08T09:00:00Z", "outputs": { "note": "front desk", "accepted": true }, "public_receipt": { "name": "Record visit", "input_summary": [{ "label": "note", "value": "front desk" }], "outputs": [ { "label": "The note you sent", "value": "front desk" }, { "label": "Whether the request was accepted", "value": true } ] } }
  • outputs 只含目前仍可安全顯示的公開 output,其他 output 一律隱藏。public_receipt.name 是 Command 名稱,input_summary 以各 input 的 description(沒有時用 input 名稱)回顯呼叫者自己送出的 input(identity 型別的 input 顯示為 {"label": …, "value_hidden": true}),outputs 則以各公開 output 的 description(沒有時用名稱)列出。
  • 任何公開 output 被扣留,或 receipt 會超過 100 個 input 或 4 KiB 的 JSON 時,public_receipt 會變成不含值的 {"name": …, "input_summary": [], "outputs": [], "withheld": true};仍然安全的公開 output 照樣留在 outputs。
  • 停在公開 assert 上的執行回 422 {"error": "assertion_failed", "message": "<作者寫的 message>", "redacted": true},而不是一般的 command_failed。其他失敗,以及不公開的 assert 失敗,維持上一節的行為。
  • 讀回這次執行的每個地方都會帶同樣的項目:限制未變時的同 key replay、GET …/executions/{execution_id} 與執行清單,各自是帶 outputs 與 public_receipt 的 redacted: true 項目。有兩個例外:被截到 65 536 bytes 上限的已儲存回應不再保留 outputs,所以該次執行的 replay 與讀取都不帶公開項目(只有第一次回應有);已儲存的結果若帶有讀取者看不到的 approval_provenance envelope(見審批 provenance),則回傳單純的 status-only receipt。權限變動後的同 key replay 仍是 409 idempotency_context_conflict(見 replay)。

哪些可以公開

撰寫(authoring)時,伺服器會從編譯後的運算式——包含 relation 篩選條件及值所依賴的其他來源——證明每個公開值只來自:

  • 字面值,以及呼叫者自己送出的純量 input;
  • 呼叫本身指名的 context:$ctx.user_id、$ctx.social_media_client_id、$ctx.company_id 與 $ctx.chatroom_id(不含由伺服器解析成員資格的 $ctx.department_id);
  • 伺服器時鐘 $now 與 $today;
  • 不指向任何 principal 的資料列 metadata:id、version、created_at、updated_at、sort_order 與 table_id(不含 created_by、created_by_client、created_by_ai);
  • 沒有 column_acl 限制、欄位本身或資料表設定中都沒有 secret_ref 綁定、且不是 formula、rollup 或 lookup 的儲存欄位;
  • 由以上來源組成的 let 值與 relation 結果。這類 relation 讀取或寫入的每張資料表,都必須列在 table_access 並設為 read: "all",且沒有 channel 範圍(SCP)規則,因為 row policy 會依程式從未指名的欄位篩選資料列。公開的 assert 也會繼承它之前每個 step 的來源,因為執行到它就證明那些 step 都通過了。它不會繼承 before_execute 規則的來源,所以它的 message 也等於告訴呼叫者那些規則已通過。relation 只有來自沒有 for_each 的 select、insert、update 或 delete step、而且不是以字面 values 為來源的 select 時才能被證明;公開 assert 之前只要有一個 step 的 relation 通不過這項檢查,這個 assert 也會以 command_public_value_unsafe 失敗。

公開 output 是純量(null、string、text、select、integer、float、decimal、boolean、date、datetime),或宣告了不超過 20 的 max_items 的純量陣列;relation output 不能公開。其他一律在 POST 與 PUT …/commands 被拒絕、並出現在 /validate 報告中,回 typed 422(phase: "author"、retryable: false、details: {},path 指出 outputs.<name> 或 steps[<index>],合計的常數大小上限則只指出 outputs);IaC apply 則以 invalid command definition: 後接同樣文字拒絕該 command 行。

error時機
command_public_value_unsafe受限或有 secret_ref 的欄位、計算欄位、不透明運算子($json_parse、$json_get、$json_stringify、$formula、$rollup、$lookup)、非純量 input、relation output、table_access 的 read 不是 all 或有 channel 範圍規則的資料表上的 relation、上述被排除的 context 或 metadata 來源,或證明無法追蹤的運算式
command_public_value_limit_exceeded沒有 max_items(上限 20)的陣列 output、超過 512 個呈現字元的常數 output、常數 output 合計超過 4 KiB、單一項目背後超過 1,000 個來源,或證明超過 50,000 個節點

每次交付都會重新檢查

每次交付公開項目時——第一次回應、replay、執行讀取與 Agent 暫存的確認結果——都會用目前的狀態重新檢查儲存的證明。Command 儲存之後才受限或加上 secret_ref 綁定的欄位、新加在來源資料表上的 channel 範圍規則、會篩選或縮小 department 資料表的 room grant,或已刪除的資料表,都會讓該項目被扣留;執行不會重跑也不會失敗,Command 也不會被標成過期。交付時同樣套用上限:值是純量或最多 20 個純量,呈現文字(項目以 , 串接)不超過 512 個字元,所有公開 output 合計不超過 4 KiB 的 JSON。失敗的公開 assert 若證明已不成立,則退回 command_failed。

Agent 與聊天 receipt

Agent 的 command 工具在已套用的執行上帶有 public_receipt 物件;公開 assert 失敗時,它的拒絕是 assertion_failed,作者的 message 放在 reason,並帶 "public_assertion": true。在聊天中,已套用 command 的 receipt 會改為陳述業務事實,而不是固定句:

「Record visit」已完成。 - note:「front desk」 - The note you sent:「front desk」 - Whether the request was accepted:是

文字值以引號呈現,URL 或像識別碼的值顯示為「系統識別資訊已隱藏」,被隱藏的 identity input 顯示為「已依您指定的對象帶入(識別資訊已隱藏)」。當 receipt 是 withheld、沒有列出任何公開 output、超過 4 KiB,或無法安全轉成文字時,會退回「操作已完成。」(The operation completed successfully.)。在純文字通道(LINE、Messenger 與 Instagram)上,這份 receipt 會接在模型自己的敘述之後,而不是只留下敘述。manager 的執行、invoker-authority 的執行,以及沒有公開項目的執行,仍是固定句(見 receipt)。

以 IaC 撰寫

command 行接受 execute_policy、execute_grants(最多 100 個 {principal_type, principal_id} 項目,宣告式套用:apply 會建立缺少的 grant、撤銷多出的 grant;請依 principal_type、再依 principal_id 排序列出,就像 export 一樣),以及在 definition 內的 authority、table_access(以 IaC 資料表 ref 為鍵,會被解析為 id)與 exclude_requester。authority: definition 而沒有 execute_policy: restricted,是 parse 階段的 400(definition_requires_restricted),也是 apply 的拒絕。Grant 的主體在公司內已無法解析時,該行會以 execute grant principal <type>:<id> not found 失敗。省略 execute_policy 代表 open,省略 execute_grants 代表撤銷所有 grant,就像省略 visibility 代表 public 一樣:請在每次 apply 都帶上這兩個 key,或改從新的 export 開始。Apply 會從套用的操作者重新推導 owner,export 則會省略每個等於預設值的 key。

錯誤

error狀態時機
command_execute_denied403受限 Command 被既不管理每一張被引用資料表、也沒有有效 grant 的呼叫者執行
definition_authority_invalid403definer owner 已不存活、不在該公司、是服務帳號,或不再管理每一張 table_access 資料表
definition_ceiling_missing403某張被引用的資料表沒有 table_access 項目
definition_requires_restricted422authority: definition 用在不是 restricted 的 Command 上
definition_ceiling_invalid422table_access 沒有恰好指名被引用的資料表

限制

  • Execute access 不是對資料表 manager 隱藏資料的方法:他們可以讀取、編輯並重新儲存這個 Command。
  • Definer Command 只能做 ceiling 允許的事,但它是替它接納的每一個人做的。請讓 table_access 保持狹窄、優先使用 filtered 讀取,並只授予你能掌控的部門或房間。
  • 這些 gate 都是即時檢查,所以「這個呼叫者現在能不能執行它」的答案,可能在暫存的 proposal 與它的 release 之間改變。
Last updated on