command line
command 把組合式的多表格指令納入 IaC 管理。與其他所有具備 state 的 kind 不同,command 是 scope 層級,不是資料表子資源:它沒有 table 欄位,state ref 是保留形式 command:{ref},而不是 {table}.{ref}。
JSONL Line
{"kind":"command","ref":"close_appointment","spec":{"name":"Close appointment","description":"Mark one appointment closed","definition":{"version":2,"inputs":[{"name":"code","type":"string","required":true}],"steps":[{"kind":"update","name":"close","table":"appointments","set":{"status":"closed"},"match":{"code":"$input.code"},"max_rows":1}],"outputs":{}}}}欄位契約
頂層對應 IacCommandLine:
| 欄位 | 必填 | 精確契約 |
|---|---|---|
kind | 是 | 固定 "command" |
ref | 是 | 裸的 authored ref,符合 ^[a-z0-9][a-z0-9_-]{0,63}$ |
state | 否 | "present" 或 "absent",預設 "present" |
spec | 條件式 | IacCommandSpec;present 必填、absent 禁止 |
本 kind 沒有 renamed_from,因此沒有純改名例外:present 行一律需要 spec,而且該 guard 的訊息沒有括號補述。
spec 只接受:
| 欄位 | 必填 | 精確契約 |
|---|---|---|
name | 是 | 非空 string,最多 64 字元;在 scope 內唯一 |
description | 否 | string,預設 "",最多 1024 字元 |
agent_enabled | 否 | boolean,預設 true——聊天室 agent 是否看得到並能呼叫這個 command |
tag_id | 否 | 同 scope 的 tag ID,1–36 字元;省略時改綁文件 system tag |
definition | 是 | Command DSL object,version 為 1 或 2 |
對 agent 隱藏一個 command
agent_enabled: false 不是停用。該 command 仍可完整地由 REST、IaC 與 trigger 呼叫,只是永遠不會被載入成 agent 工具——所有 agent 端的 command 查詢都帶 agent_enabled IS TRUE 過濾。適合用在「可以被呼叫,但不希望被建議」的破壞性或維運專用 command。
這個欄位和 name、description 一樣,是三方比對的一等成員:
- 在文件中翻轉它會規劃成
update,changes內帶{"field": "agent_enabled", "from": true, "to": false}。 - 在 REST 端被翻轉會以
agent_enabled出現在drifted_fields。 - Apply 在
create與update兩條路徑上都會寫入它。
Export 是預設值即省略:只有被隱藏的 command 才會輸出 "agent_enabled": false;預設可見的 command 完全不輸出這個鍵,讓此欄位存在之前寫的文件維持 byte-stable。
省略這個鍵不代表「不要動它」,而是代表 true。用早於這個欄位的舊文件重新 author 一個已隱藏的 command,會規劃出一個把它重新公開的 update。要讓 command 保持隱藏,就在每次 apply 都帶上 "agent_enabled": false,或改從新的 export 開始。
Definition 以 ref 撰寫
REST 的 definition 不能原封不動貼進文件,反之亦然。在 IaC 形式中:
- 步驟的
table值是IaC 資料表 ref,對本文件或 IaC state 解析,絕不是即時資料表 UUID。 - 欄位參照使用顯示名稱、column ref,或明確的
{"$column": {"source": …, "ref": "table.column"}}節點;data/match的鍵會在 apply 時正規化為col_<hex>。 - Callback 的
secret_ref必須等於callback:{該 callback 的名稱}。 - 身分型別的輸入字面值可帶
$user:/$smc:token。
運算式預算由 REST 使用的同一個 compiler 執行:每個 row 運算式 256 個節點,每個 where 深度 12/128 個節點,case 為 8/64,rule 為 8/128,整份 definition 8192 個節點,JSON 深度 64/16384 個節點。另外 mode: "write" 以「等於預設就省略」的方式儲存,所以 write definition 永遠不會把這個鍵回吐——不要寫它然後期待在 export 看到。完整 DSL 契約請見 commands API 參考。
版本:寫 v1、匯出 v2
Command 的 IaC plan 會感知 DSL 版本。
- 對可安全升級的 v1 程式,export 會輸出保持行為的 v2 形式。
- 若升級會失真,則原樣保留 v1:帶隱含 body 的舊式 callback、產生的 capture 名稱超過 32 個字元,或輸入名稱與產生的步驟名稱相撞。
- 比較時會把 v1 與 v2 正規化,因此 export 的文件重新匯入是
noop。
你以 v1 撰寫的 command,在 export 時看到 version: 2 是預期行為,不是一次編輯。不要把它「還原」成 v1;正規化器已經把兩者視為相等。
dependency_contract 是伺服器建立的即時狀態,會在正規化比較前被剝除,所以 export 後重新匯入不會看起來像 definition 被改過。
Ref 與身分規則
State 存的是 command:{ref},resource_id 為 command UUID。這個形式與 table、child ref 共用文件層級的重複 ref 檢查,因此兩行 command 宣告同一個 slug 會碰撞:
duplicate ref 'command:close_appointment' (already declared in this document)以 . 切割 state ref 來推導父資料表的工具,會在 command(command:<ref>)與 insight selection(insight.<chatroom uuid>)上出錯。兩者都不是資料表子資源。
認領需要完全相符
認領是以 scope 內的名稱進行,而且不像 table 那樣有「先認領再更新」。若同名的 live command 存在但 spec 不同,該行會直接失敗,而不是認領後修正:
command adoption requires the live command to match the declared spec; export the live command or use a distinct name
ambiguous adoption: 2 live commands named 'Close appointment'兩者都是 diff 階段錯誤:apply 回傳 200,只略過該行。第一種的處理方式是先把 live command export 出來,以那份文字為起點。
Tag 綁定
明確的 tag_id 必須是本 scope 的 tag,而且必須涵蓋 definition 引用的每一張表。省略 tag_id 時,apply 會把 command 綁到文件的 system tag,並把每張被引用的表指派給它——這需要非空的 header.system。因此,沒有 system(system: "")的文件無法在不提供 tag_id 的情況下宣告 command。
權限
具備 scope 存取權並不足夠。在認領以及每一次 present apply 時,操作者都必須是該 command 相依集合中每一張表的 table manager,而且那些表都必須在本 scope 可解析。自 2026-07-28 版起,刪除(state: "absent")是例外:table-manager 檢查失敗時——綁定的表在本 scope 已解析不到活表,或你不是它們的 manager——刪除分支會改看 command 自身 scope 錨點的權威(聊天室建立者或管理員、部門管理員、或租戶錨定的公司管理員),鏡射 REST 的殭屍 command 復原。所有失敗都回傳同一則刻意不透露細節的訊息:
command 'close_appointment': table no longer resolves live它不區分「表不存在」與「表不屬於你」。這是刻意的,與 REST 的 command 撰寫路徑一致。
生命週期與規劃
Present 行可規劃 create、adopt、update 或 noop;state: "absent" 規劃 delete,會軟刪除受管理的 command。沒有 move——本 kind 沒有 renamed_from。
被遮罩的 callback 值無法用來建立 command。Export 的 callback 密鑰素材會以 <REDACTED> 回來並單獨計算指紋,因此帶遮罩的行可以更新既有的受管理 command,卻永遠無法建立一個:
redacted callback values require an existing managed command; replace them before recreating it
redacted callback values require an existing managed command; replace them before creating in a fresh scopeLive callback 密鑰變動會以 definition 出現在 drifted_fields,這也是為什麼有人在 REST 改了 callback URL 或 header 之後,一個完全沒被改動的 command 仍會回報 definition drift。
支援的 dependency maintenance 不分 DSL version。IaC column 與 rule 行使用一般政策:先選 mutation 前 stored contract 仍 valid 的 commands,在同一 commit 刷新 mutation 後仍可編譯者;若 mutation 使某 command 失效,mutation 優先,該 command 保持 stale。非資料庫 prepare/compile/closure/concurrent-edit failure 會降級或略過,不 veto column/rule line;DB lock/deadlock error 仍可能中止。IaC trigger 行使用更強政策:candidate graph 與每個 selected command refresh 都必須成功,trigger、history、IaC state 與 refreshed definitions 一起提交或一起回滾。兩種政策都讓原本 stale 的 command 維持 stale。
在同一次 IaC apply 中,成功的 column、rule 或 trigger maintenance 都可能在較後面的 command 行執行前推進 dependency digest。Executor 只接受這些精確、由伺服器擁有的 digest 推進;任何外部或其他非預期 before-fingerprint drift 仍會產生 iac_plan_stale。
Apply 階段錯誤
以下錯誤出現在 200 apply 回應的 per-line 錯誤中;其他行仍會執行。
command 'close_appointment': table no longer resolves live
command 'close_appointment' has pending staged executions
command 'close_appointment': tag not found in this scope
command 'close_appointment': tables outside tag: ['<table id>']
command 'close_appointment': document system tag is required
a command named 'Close appointment' already exists in this scope
command changed after planning; run plan again
command 'close_appointment' is referenced by table triggers (command_referenced_by_trigger): table '預約' (<table id>) trigger '完成通知' (<trigger id>)
Concurrent write conflict (lock); please retry the request.當該行的 before-fingerprint(resource、state、tag、dependency digests)於 plan 與 apply 之間變動時,command changed after planning; run plan again 會帶 IacLineError.code == "iac_plan_stale"。若套用該行時遇到 MySQL 1205 或 1213,則改帶 code == "retryable_lock_conflict",detail 是淨化後的 Concurrent write conflict (lock); please retry the request.。上面其他 diagnostics 的 code 不設值。兩者都是 200 內的 per-line 訊號,不是文件層級的 409;任何重試都要從新的 plan 與 apply 開始。請見 plan 與 apply。
a command named '<name>' already exists in this scope 現在只計活列,與 REST 一致——軟刪的 command 立即釋放名字(command 沒有還原機制),所以「先一行 delete、之後再用同名 create」是合法順序。
trigger 引用守衛在 state: "absent" 時照樣生效,殭屍 command(表已消失)也不例外——仍被活表(或垃圾桶中可還原之表)trigger 呼叫的 command 是唯一擋住刪除的東西,對應 REST 的 409 command_referenced_by_trigger。
具備核准機制且有 staged execution 進行中的 command,會擋住自己的 IaC 行,直到該 staged 變更被處理完。
驗證錯誤
state=absent lines carry no spec (table lines are the sole adopt-then-delete exception)
state=present lines require a spec
unknown table ref 'appointments'
duplicate ref 'command:close_appointment' (already declared in this document)工作台會檢查生命週期 guard、spec 欄位契約,以及 definition.steps[].table 指名的資料表 ref。它看不到 IaC state,所以指向由先前 apply 管理之資料表的步驟,在本機會回報成 unknown table ref,必須以伺服器 plan 確認。其餘所有 DSL 規則——運算式預算、步驟形狀、參照解析——都由伺服器 compiler 判定。
動手試試
在 IaC 工作台宣告一張表、一個欄位與上面的範例 command。把 definition.steps[0].table 改成未宣告的 ref 觀察本機 validate 錯誤,修正後對測試 scope 執行唯讀 plan,並確認 action ref 顯示為 command:close_appointment。