Skip to Content

trigger line

trigger 管理 table 的 after-commit automation。它不會 veto 原始寫入;actions 會在事件提交後才非同步執行。執行權限依 action 而異:row-event 的 record writes 使用造成該 run 的 principal 之即時 ACL,scheduled automation 使用 system authority,而 invoke_command 會重新檢查 trigger 作者當下的權限。created_by 主要作 audit attribution,不是所有 action 的通用授權;notify 也會以該作者作為訊息身分,並要求 user row 仍存在。IaC line 讓每一條 trigger 有可移動、可審查的 ref。

JSONL Line

{"kind":"trigger","table":"orders","ref":"notify_on_create","spec":{"name":"Notify on create","on":"created","when":[{"column":"status","op":"eq","value":"open"}],"actions":[{"type":"notify","message":"New order: $row.order_no"}]}}

欄位契約

頂層對應 IacTriggerLine:必填 kind: "trigger"tablerefstate 可選且預設 present;renamed_from 可選;present 需要 object spec(純 rename 除外),absent 禁止 spec。

IacTriggerLine.spec 在 IaC Pydantic model 中是 Dict[str, Any];真正的精確 trigger payload 由既有 TableTriggersPayload/shared validator 處理:

欄位必填契約
oncreatedupdateddeletedrestoredschedule
actions1–5 個 action objects;type 為 webhookapi_callcreate_recordupdate_recordnotifysend_channel_messageinvoke_commanddelete_recordmaterialize_slotssubmit_sandbox_job
namestring,最多 128 字元
when最多 5 個 predicates;每個 column 使用本 table column ref
transition只適用 on: "updated"{"column": ref, "from"?: value, "to"?: value}
schedule條件式on: "schedule" 時必填;其他事件禁止。typedate_column_reachedintervaldailycron。在保留的 trigger id 下更改 type 會被 schedule_kind_change_requires_new_trigger_id 拒絕 — 請刪掉該行,並把新 kind 寫成一個新 trigger;詳見排程觸發器

Action 有自己的 type-specific 欄位:webhook/api_call 使用外部目的地;create/update record 使用 target/data;notify 使用通知目的與 message。不要 authored server idcreated_by;trigger line ref 與 apply actor 分別處理 identity/ownership。每張表的合併上限仍然限制一份文件能宣告多少:每表 50 個 trigger、10 條 rule,且會把佔用名額的未受管理項目一起計算。完整 shape 請查閱規則與觸發器 API

新 action 類型的 authored 形狀

有六個 action 類型具有重要的 IaC authored 欄位或 ref 翻譯。寫成儲存的內部欄位形狀會破壞可攜性或收斂。

send_channel_message 只有在 createdupdated 才能選附件來源。Authored 形狀使用 1–5 個不重複的 attachment refs;apply 會轉成內部 key,export 會轉回 ref:

{"type":"send_channel_message","recipient":"created_by_client","message":"$row.order_no 的附件","attachments":[{"column":"invoice"}]}

所選快照最多可解析出 20 個不重複 blobs。LINE/Messenger/Instagram 先送文字再逐 blob 送出;agent 使用一則組合訊息。逐 part receipt 讓 retry 從已完成 parts 後續跑。

invoke_commandref 指名 command,不是 command_id。同文件的前向參照是合法的——executor 會把該 trigger 行延到 command 行套用之後——也接受同 scope 內 live command 的名稱或 slug:

{"type":"invoke_command","command":"close_appointment","inputs":{"code":"$row.order_no","actor":"$user:dr.chen"}}

身分型別的輸入可帶 $user: / $smc: token;$row.{column ref} 輸入會在觸發當下依觸發列渲染。

delete_record 以字面撰寫,不做 ref 翻譯:

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

materialize_slots$table:{ref} 形式指名目標資料表。排程之前值得先知道它自己的上限:每次執行最多掃描 1000 個 live 樣板列(超過會在插入任何時段之前失敗)、每個 tick 最多插入 1000 個時段(被截斷算成功,不是錯誤)、92 天的展望期、時段粒度 5 到 1440 分鐘、weekday 值為 monsun,來源時間 cell 為 24 小時制 HH:MM

api_call 的渲染上限限制觸發當下的 $row. 展開:樣板長度 2000、最多 20 個 header、設定時 body 8192 位元組,渲染後 body 上限 256 KB、URL 與 header 各 8192 位元組。方法為 POSTPUTPATCHDELETEGET,只有 POSTPUTPATCH 可帶 body。

submit_sandbox_job 釘住 published、active 且不要求 confirmation 的 Sandbox task version,並且只適用 createdupdated

{"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.order_no","amount":"$row.amount"}}

只有 table 本身有 chatroom 時才可省略 chatroom_id;department/company table 必須 authored。timeout_seconds 預設取 pinned version,範圍 1–604680。input 是 JSON object,template 最多 8 層、每個 string leaf 最多 2000 字,並受 Sandbox canonical JSON 的 1 MiB/深度 64/100000 nodes 上限約束。巢狀 $row.{column ref} string 可經 export/apply round-trip;剛好等於 token 的 leaf 保留 JSON 型別。Apply 與 fire time 都要求同公司的 live room、active task、published/active 且 requires_confirmation: false 的 version,以及可執行權限;fire time 還會在 input staging 前後重查 causing principal、來源 row read/hidden-column/channel policy 與不可變快照。

Ref 與身分規則

orders.notify_on_create 是 qualified identity,與同 table 的 column、rule、view、public_read refs 以及固定的 client_access identity 共用 namespace。Spec 中 when.columntransition.column、action 的 data keys/target.via,以及 $row.<ref> token 都應使用 authored column refs;plan 會翻成 live internal keys或保留可安全插值的 template。

泛用本機 walker 會檢查 columnviadata keys 等明確 ref 位置。外部 URL、header 與 message 裡的 template 還要由 server validator 檢查,不能把任意普通字串當成 column ref。

身分 token 與 $row token

Principal 欄位上的 when predicate 可帶身分 token,由 plan 與 apply 兩側的同一組共用 helper 解析。在 usersocial_client 欄位上,這是指 $user:$smc: 解析成原始 ID;在 principal 欄位上,則是 $user:$smc:$room: 解析成該欄位儲存的帶標籤 cell("value": "room:<id>",運算子維持不變)。identity 型別的 invoke_command 輸入常值只接受 $user:$smc:,而且一律解析成原始 ID——沒有 identity:principal 這種輸入型別。未被替換的 token 會驗證通過卻在觸發時永遠比不中——變成一條靜默失效的 trigger——因此 plan 會把 token 失敗當成 validate 錯誤回報:

trigger when column 'assignee': principal token '$user:bob': user 'bob' is deleted

$row.{column ref} 樣板 token 則是反方向。Export 會把儲存的 $row.{col_hex} 形式改寫成 ref,涵蓋 api_callurlheadersbodynotifysend_channel_message 的訊息、line_flex 字串,以及整個 submit_sandbox_job.input;plan 與 apply 再把 ref 翻回內部 key。send_channel_message.attachments[].column 也會以明確 column ref round-trip。

警告: $row. 的翻譯刻意不是 fail-closed。無法解析的 token 會原樣保留,好讓舊有以內部 key 撰寫的文件、以及只是「看起來像 token」的自由文字繼續運作。因此 api_call body 或 notify 訊息裡打錯的 column ref 會靜默通過 plan,並在觸發時渲染成字面文字。

生命週期與規劃

Present line可規劃 createadoptmoveupdatenoop;absent 規劃從 table triggers 集合刪除。Apply 透過 validated full-list PUT path merge-upsert,不會用這一行抹掉未受 IaC 管理的 triggers。

手寫 trigger 現在會收斂

手寫的 trigger 過去會永遠重新規劃成 update,因為 store 會寫入作者從未輸入的欄位。現在 differ 與 executor 共用同一個正規化器,對雙方對稱地補上那些欄位:

  • transition 補上明確的 fromto,預設為 null
  • create_recordupdate_record action 補上所屬資料表的 table_id
  • 排程的 offset_minutes: 0timezone: "UTC" 會被移除,因為 store 本來就省略它們。

結果是手寫的 trigger 重新 plan 會是 noop。如果你一直在對付某條永遠不收斂的 trigger 行,原因就在這裡——而且不需要改文件。

最後一項有個值得知道的推論:在排程中釘住 offset_minutes: 0timezone: "UTC" 是合法的,但 store 會把兩者丟掉,所以文件與 live 在這些鍵上的差異是正規化產物,不是意圖。

那個永遠存在的 update 不只是噪音。它每次 apply 都會重新產生 action ID,並改變讀取該表之相依 commands 所儲存的 trigger digest。現在穩定地沿用 action ID,已移除這種人造變動。

當 IaC trigger 行變更 live trigger 集合時,apply 也會建立候選 dependency closure。每個新的 target table 都必須符合所有在編輯前 stored dependency contract 仍 valid 之相依 commands 的 tag closure,不限 DSL version。伺服器會用候選 triggers 重新編譯那些 commands;若 command 的 stored contract 已因其他原因 stale,則刻意維持 stale。Trigger 集合、schema history、trigger IaC state 與刷新後的 command definitions 會在同一個 transaction 中提交。只要 validation、refresh、state 或 commit 失敗,該行的整個 bundle 都會回滾,而 apply 繼續處理其他行。

Plan 與本行執行之間若發生外部 trigger drift,會產生 per-line code: "iac_plan_stale",detail 為 trigger changed after planning; run plan again。相對地,若本行成功並推進相依 command 的 dependency digest,同一次 apply 中較後面的 command 行會接受這個精確、由伺服器擁有的推進,不會產生假的 iac_plan_stale

Trigger action 可以寫入另一張表、呼叫外部服務或送通知,具有比 spec diff 更廣的影響面。Plan 審查必須核對 actor、event、target refs、外部 URL、action data 與 chain/depth 保護;free-dict 的完整 trigger validator 仍在 apply 持久化路徑執行,本機 parse 成功也不驗證目的系統可用性。

驗證錯誤

state=absent lines carry no spec (table lines are the sole adopt-then-delete exception) state=present lines require a spec (unless a pure rename via renamed_from) unknown table ref 'orders' duplicate ref 'orders.notify_on_create' (already declared in this document) unknown column ref 'orders.status' {kind} cap exceeded ({final} > {cap}); unmanaged {kind}s occupying slots: [names]

上限訊息是 validate 錯誤,回報在該表的第一行 rule 或 trigger 上;對全新的表則完全跳過——沒有 live 表就沒有東西會被撐破。

前兩類 lifecycle detail 目前由工作台本機穩定攔截;錯誤 event、actions 數量與 action-specific fields 由 apply 的 downstream trigger validator 回報。精確本機字串來源:components/iac/parse.ts

動手試試

IaC 工作台建立範例 trigger,把 when.column 改成未知 ref,修正後對測試 scope 執行唯讀 plan。Apply 前逐項核對 action target 與外部副作用。

Last updated on