Skip to Content

觸發器動作型別

一個 trigger 會依陣列順序執行 1 到 5 個 action,遇到第一個失敗就中止其餘動作。共有十種 action,其他 type 一律回 400,錯誤訊息會列出全部十種。

上限以資料表為單位:最多 50 個 trigger、每個 trigger 1 到 5 個 action、最多 5 個 when 條件、chain depth 最多 3、每條 chain 的 generated-run 預算 1,000、每個 record-write action 最多 100 筆目標列、模板字串上限 2000 字。Webhook URL 與 header 值也受同一個 2000 字模板上限約束,header 最多 20 個;header name 不得為空白,也不得含 :、CR 或 LF。

Action 身分,以及為什麼一定要原樣送回

每個 action 都有伺服器產生的 id。讀取時會把它放進 action 物件的 idact_<12 hex>);儲存端則另外保存一份與 actions 一對一對齊的頂層 action_ids 清單。

這件事之所以重要,是因為失敗續跑與各 action 的冪等鍵都掛在這個 id 上,而不是掛在陣列位置上。重試時是用 id 去比對先前的 receipt,所以在一次 run 失敗到重試之間插入或重排 action,不會把已完成 action 的 receipt 錯位到別的 action 上。反過來說,用戶端存 trigger 時沒有保留回傳的 id,伺服器就會重新產生,所有 pending 或 failed 的 run 都會失去續跑點:舊的 receipt 與新的 id 完全對不起來。

Worker 每次嘗試都會重新讀取目前的 trigger 設定,所以在 run 處於 failed 狀態時修改 trigger,會改變重試實際執行的內容。

Receipt

每個 action 不論成功或失敗都會在 run 的 action_results 追加一筆 receipt,且每筆都帶自己的 action_id。失敗長這樣:

{ "type": "create_record", "ok": false, "detail": "…截斷到 300 字…", "action_id": "act_0a1b2c3d4e5f" }

Run 本身的 error 截斷到 1000 字。失敗的 run 不會自動重試:復原方式是管理者的 retry 端點,或維運的 requeue 掃描。見執行紀錄與重試

webhook

{ "type": "webhook", "url": "https://integrator.example/hooks/orders", "headers": { "X-Source": "teamsync" } }

把 trigger 當下的資料列快照 POST 到一個靜態 HTTP(S) host。不受 ACL 閘門限制。

api_call

{ "type": "api_call", "method": "POST", "url": "https://integrator.example/orders/$row.訂單編號", "headers": { "Authorization": "Bearer …" }, "body": { "record_id": "$row.訂單編號", "amount": "$row.金額" } }

支援 GET、POST、PUT、PATCH、DELETE。Host 必須是靜態的,只有 path 與 query 可以插值。URL、headers、body 的模板在儲存時會正規化成內部欄位鍵,所以欄位改名不會壞。不受 ACL 閘門限制。

create_record 與 update_record

{ "type": "create_record", "table_id": "22222222-2222-4222-8222-222222222222", "data": { "來源訂單": "$row.訂單編號" } } { "type": "update_record", "target": "$row", "data": { "狀態": "已同步" } }

明確指定的 target table 遵循共用的同公司 hierarchy:可以留在 host table 的 scope;chatroom host 可指向自己的 department 或 company;department host 可指向自己的 company。向下、sibling、無關 department 與跨公司 target 會以刻意統一的 target table not found or not accessible 拒絕。省略 table_id 時使用 host table。

update_record"$row" 指向觸發它的那一列,或用 { "via": "<link 欄位>" } 指向一跳之外的目標。兩者都受執行來源的 ACL 閘門限制,見下面的執行時權限。兩者都不能指向帶 require_approval 規則的資料表。

notify

{ "type": "notify", "chatroom_id": "11111111-1111-4111-8111-111111111111", "message": "新訂單:$row.訂單編號" }

對同公司的聊天室發送訊息。有兩個坑:訊息以原文儲存,所以被引用的欄位改名時模板會默默失效(其他 action 都不會);而這個 action 需要 trigger 作者的使用者資料仍然存在,否則 run 會記下 notify skipped: trigger owner user no longer exists

send_channel_message

依觸發的那一列解析出一位社群客戶,推送一則訊息。

{ "type": "send_channel_message", "recipient": "created_by_client", "message": "您的訂單 $row.訂單編號 已可取貨。" }

recipient 可以是字串常值 "created_by_client"(在觸發當下讀取線上資料列的 created_by_client),或 { "column": "<欄位>" }(從 trigger 當下的快照取值)。該欄位必須是 stringsocial_client 型別。user 欄位會被拒絕,因為它存的是內部使用者 id,不是通訊管道身分;principal 欄位也會被拒絕,因為它的 cell 帶標籤、還可能指向一個聊天室——從帶標籤的 cell 做投遞刻意不在範圍內。

觸及範圍是整間公司,不是單一聊天室。 在欄位模式下,收件客戶自己的聊天室必須存在、未刪除,且屬於這張表解析出的公司;它不必是這張表所在的聊天室,所以這個 action 可以發給同公司任何聊天室的任何客戶。把它指向使用者可以編輯的欄位之前,請先確認這真的是你要的行為。

憑證一律取自收件客戶自己聊天室的通道設定,不會從 action 設定取。缺少設定檔屬於永久失敗,receipt 會明說:permanent — do not retry: no {platform} profile for client {id}。支援的平台只有 linemessengerinstagramagent,其他一律讓該 action 失敗。

message 為必填,是 $row 模板,設定時上限 2000 字,並會正規化成內部鍵以免改名壞掉。執行時渲染結果超過 8192 bytes 會讓該 action 失敗。

選填的 line_flex 帶 LINE Flex 的 contents 物件(bubble 或 carousel),只會送給 line 客戶,其他平台退回成純文字。設定時的上限:必須是 JSON 物件、可序列化、序列化後不超過 10240 bytes、每個字串葉節點不超過 2000 字、嵌套不超過 8 層。執行時渲染後的 line_flex 超過 262144 bytes 會讓 action 失敗,而 Flex 的 altText 是被裁切到 400 字,不會失敗。在 Flex 裡,剛好等於 "$row.<欄位>" 的葉節點會渲染成字串(浮點數變成 "99.5"、null 變成 ""、dict 或 list 變成緊湊 JSON),而非代號的常值會保留原本的 JSON 型別,所以數值型的 Flex 屬性仍然可用。

只有 createdupdated trigger 可以用 attachments 選取 1 到 5 個不重複的 attachment 欄位。每一項必須剛好是 { "column": "<attachment 欄位>" }

{ "type": "send_channel_message", "recipient": "created_by_client", "message": "$row.訂單編號 的附件", "attachments": [ { "column": "發票" }, { "column": "照片" } ] }

Action 會依欄位順序從不可變的觸發快照讀取 blob IDs,保留 cell 內順序、移除重複 ID,解析後超過 20 個 blobs 就拒絕。執行時會重新驗證 run 的 table、record、version/history event、每個來源欄位仍是 attachment,以及 blob 的公司歸屬。外部 LINE、Messenger 與 Instagram delivery 還要求每個 blob 具備永久 HTTP(S) public URL;agent delivery 會直接關聯已解析的 blobs,完全不解析該 public URL。所選 cells 全空是合法的,只會送文字。deletedrestored 與 schedule trigger 不能設定 attachments。

每個送達的 part 都會在客戶聊天室留下 CRM 收件匣可見的真實 assistant message。LINE、Messenger 與 Instagram 的 delivery plan 是先送 text/Flex,再為每個 blob 各送一則 provider message;agent 則用一則 text-plus-blobs 組合訊息。Receipt 會回不含 URL 的 parts、逐 blob attachmentsassistant_message_idsdelivery_plan_hash。每個 part 成功後都持久化進度,因此手動 retry 會跳過已成功 parts;部分送出後若 plan 漂移,retry 會 fail closed,必須建立新 trigger run。Receipt 的 message IDs 是 TeamSync row IDs,不是平台 IDs。LINE parts 帶 deterministic retry key,可在 LINE 的 24 小時視窗內去重;Messenger 與 Instagram 仍是 at-least-once。

Warning send_channel_message 在 delivery time 不會重新檢查 causing principal 的資料表 ACL 或 channel-scope policy。基礎 text/Flex 的守門是執行時對收件客戶的公司檢查,因此任何能寫出會觸發此 trigger 資料列的人,都能造成 customer-facing 文字送出。加上 attachments 會增加不可變 source record/history、attachment column、同公司 blob 檢查(外部 channel 另有上述 public URL 檢查),但不會增加 causing-principal 的 row-read、hidden-column 或 SCP 授權閘門。

invoke_command

複合指令接進自動化流程。

{ "type": "invoke_command", "command_id": "55555555-5555-4555-8555-555555555555", "inputs": { "order_id": "$row.訂單編號", "warehouse": "TPE-1" } }

command_id(uuid)與 command(名稱)只能給其中一個。名稱會在儲存時沿著資料表的範圍鏈解析:先是這張表自己的聊天室、再是它部門上不綁聊天室的指令、再是公司層不綁範圍的指令,最近的範圍優先,已軟刪除的排除。儲存端一律保存 command_id

inputs 把指令已宣告的輸入名稱對應到「型別檢查過的常值」(檢查邏輯與 execute 端點完全相同)或「剛好等於 "$row.<欄位>" 的代號」。字串中間插值會被拒絕。指令標為必填的輸入全部都要對應,未宣告的名稱回 400,而 null 只有在輸入規格允許 nullable 時才可以。

可以餵 $row 代號進指令輸入的欄位型別只有這些:stringtext 進字串輸入,integerfloatbooleandatedatetime 進各自型別,usersocial_client 進身分輸入或字串,principal 只能進字串輸入——identity: 型別的參數預期的是裸 id,每次執行都會拒絕或錯誤解析 user:u1。其他型別一律不能當代號來源,包含 jsonattachmentselectmulti_selectlinkinterval 與所有計算型別。另外,沒有資料列的排程(intervaldaily)也不能用 $row 輸入,因為那種執行本身沒有資料列。

由 trigger 呼叫的指令走的是真正的指令管線,但權限是以 trigger 作者當下的有效權限準備,不是造成觸發的人,也不是系統帳號。作者必須仍是這張表所屬公司裡活著、已驗證的帳號,而聊天室層的指令還要求他仍綁在那個聊天室。執行範圍由指令自己的範圍決定,指令自己的編譯合約、ACL 與 SCP 準備、核准掃描、冪等與稽核紀錄全部照常適用。

冪等鍵由 run 與 action id 推導,所以崩潰視窗內的重試會重播先前儲存的回應,而不是把指令執行第二次。Receipt 會帶 command_idexecution_id 與步驟數;execution_id 就是 trigger run 與指令執行之間的稽核連結。

指令那一側因此多了三個 409

你嘗試做的事錯誤
刪除任何 trigger 引用中的指令,包含已被丟到垃圾桶的表上的 triggercommand_referenced_by_trigger,附上引用它的資料表與 trigger
把被引用的指令在寫入與查詢模式之間切換command_mode_trigger_conflict
讓被引用的指令新增一個指向帶 require_approval 資料表的引用approval_trigger_conflict

查詢模式的指令永遠不能當目標:設定時就是 400

submit_sandbox_job

由不可變的 createdupdated 資料列快照排入 Sandbox run:

{ "type": "submit_sandbox_job", "chatroom_id": "11111111-1111-4111-8111-111111111111", "task_version_id": "55555555-5555-4555-8555-555555555555", "timeout_seconds": 900, "input": { "order_id": "$row.訂單編號", "amount": "$row.金額" } }

它只適用 createdupdatedtask_version_id 必須釘住同公司的版本:task 為 active、version 為 published 且 active,並且 requires_confirmation 為 false。它也不得有 output_policy:Custom Table writeback credential 即使該旗標為 false 仍屬 confirmation-grade,而 trigger 沒有稍後的人類確認輪。設定時會以 submit_sandbox_job cannot use a task version that declares a custom-table writeback output_policy 拒絕。chatroom_id 預設為資料表的聊天室;department/company table 必須明確提供。Room 必須是同公司且未刪除,live 設定者必須能在該 room 執行此 task。timeout_seconds 預設取 version timeout,必須是 1 到 604680 的整數。

input 必填且必須是 JSON object。Template 最多嵌套 8 層、每個字串 leaf 最多 2000 字;canonical input 合約另限制 UTF-8 JSON 最多 1 MiB、深度 64、節點 100000,並拒絕 NUL。巢狀字串 leaf 裡的 $row ref 會被正規化。執行時 manifest 的 row 只暴露明確挑選的值,不會複製完整 row 或 diff;剛好等於 token 的 leaf 會保留原 JSON 型別。

設定中的 IDs 不是權限。Worker 在 staging input.json 之前與之後都會重新載入 causing principal、company/table/run、來源 row 與不可變快照、hidden-column 與 record-read ACL、channel-scope 權限、room、task 與 pinned version;撤權或快照漂移會在 run 排入前 fail closed。Fire-time 檢查也會拒絕後來新增 output_policy 的版本,訊息是 submit_sandbox_job denied: task version declares a custom-table writeback credential; trigger submissions cannot mint one。Receipt 回傳 run_idtrigger_run_idtask_version_idinput_digestrun_statussubmission_source: "custom_table_trigger";由 run/action 推導的 idempotency key 避免 retry 建立重複 Sandbox runs。人類專屬的 credential lane 見 Sandbox 寫回 Custom Table

delete_record

{ "type": "delete_record", "target": "$row" }

只軟刪除觸發它的那一列。target 必須剛好是 "$row":不支援連鎖目標({ "via": … }),也不接受 data。在 deleted trigger(列已經不在了)、沒有資料列的 interval 與 daily 排程,以及 trigger 自己的表帶 require_approval 規則時,都會被拒絕。

它走真正的刪除路徑,所以資料列鎖、edit filter ACL、核准保險、unique 條目清理與歷史紀錄全部照常,而且會在下一層 chain depth 觸發這張表自己的 deleted trigger。對已刪除的列再刪一次是冪等成功,receipt 會回 "deleted": false

materialize_slots

只能用在 on: "schedule"schedule.type: "daily"。它會在滾動視窗內把班表資料列展開成同公司目標表裡實際可預約的時段列。Receipt 會回 createdskipped_existingtruncated 與這次掃描的 window。完整設定見產生可預約時段

執行時權限

會寫入資料列的 action 由「是什麼造成這次執行」決定權限,而判斷依據只看事件名稱:

觸發來源create_recordupdate_recorddelete_recordmaterialize_slots 的權限
createdupdateddeletedrestored造成該次寫入的 principal
schedule系統

在由使用者造成的執行裡,實際檢查的是:create_recordmaterialize_slots 需要目標表的 insert 權限,update_recorddelete_record 需要非 none 的 edit 權限。被拒絕時會變成 ok: false 的 receipt 並讓 run 失敗,沒有新的 HTTP 錯誤合約。

造成觸發的 principal 必須仍是活著的帳號:被軟刪除、停用或未驗證的使用者,即使資料列還在,也會失去執行時的寫入權限。agent 平台的社群客戶會委派給它 external id 指名的使用者,而那個帳號的存活與公司歸屬同樣要成立。

只有三種情況保留系統權限:由排程造成的執行、payload 早於這次變更部署的執行,以及造成寫入時既沒有使用者也沒有客戶 id 的執行(種子資料、匯入與內部機制都算這一類)。

由 trigger 寫出的資料列仍然標記為 trigger 的 created_by。權限與歸屬現在是兩件不同的事。

Action 內容裡可以用的代號

只有 $row.<欄位> 這一套。Row policy 的 $me$me.department$today$nowwhen 條件與時段產生器的來源過濾裡都會被拒絕;而在 action 內容裡,字面上的 "$me" 就只是一個字串,會被原樣寫進去。未知的 $row.<某個東西> 在儲存時原樣保留,執行時就照字面渲染出來,因為替換表只由真實存在的欄位建立。模板請自己校對。

在一次 run 內,session 會被固定成 SCP 拒絕姿態(因為 trigger 沒有 acting chatroom),也會固定成一種核准情境:符合的 require_approval 規則會直接拋衝突,而不是暫存一筆變更;否則每次執行與重試都會產生一筆垃圾覆核流程。

這些 action 在 JSONL 文件裡的寫法,包含那裡才有的 $table: 與身分代號,見 trigger 行類型

Last updated on