JSONL IaC 概念
JSONL IaC 把一組彼此關聯的表格、欄位、規則、觸發器、檢視、授權、用戶端存取設定、資料列、組合式指令、逐室洞察選擇與免驗證公開讀取 token,寫成一份可版本控制的宣告。它適合管理「多個資源共同構成一個系統」的情境,不必把一連串命令的執行順序當成唯一真相。
IaC 是無條件可用的。Plan、apply、state 與 export 永遠開啟;沒有需要先啟用的 feature flag,舊有的「flag 關閉時回 400」回應也已經在所有 scope 上消失。CUSTOM_TABLE_IAC_MAX_BYTES、CUSTOM_TABLE_IAC_MAX_LINES、CUSTOM_TABLE_IAC_MAX_TABLES、CUSTOM_TABLE_IAC_MAX_RECORDS 與 CUSTOM_TABLE_IAC_SYNC_RECORD_LIMIT 仍然存在,但它們是容量調節參數,不是開關。自後端 5.22.0 起,CUSTOM_TABLE_IAC_SYNC_CHANGE_LIMIT 也是其中之一:它決定 apply 在 request 內最多可建立、更新、刪除或搬移多少個資源,超過後文件就會改走 ticket。
有一項輸入限制刻意不屬於它們:每個實體行的 JSON 巢狀深度上限是 128 層 [/{,由寫死在 parser 內的防護擋下,沒有任何環境變數可以調高或調低。深度是就整行測量的,包含行本身的外框 —— record line 最外層的 { 就是第 1 層 —— 字串字面值內的括號不計入。它同時是每行的第一道檢查,排在 JSON 解碼、header 唯一性規則與 record/line/table 各項 cap 之前,因此一個過深的行即使同時是壞掉的 JSON、或本來也會撞破某個 cap,回報的仍是巢狀中止:
line 3: excessively nested JSON structure (max depth 128)只有文件層級的位元組上限與 UTF-8 檢查比它更早執行。parse 階段其餘的錯誤目錄見錯誤階段。
為什麼使用 IaC
一般 API 呼叫描述「現在做一件事」;IaC 文件描述「最後應該長成什麼樣子」。穩定的 ref 讓規劃器在環境間解析同一套關係,而 plan 先把 live 狀態與宣告的差異攤開。這帶來三個實際好處:
- 可審查:JSONL 與 plan diff 都能進入變更審查,建立、更新、移動與刪除不再藏在腳本控制流程裡。
- 可重複:相同文件可套用到新的 chatroom scope;套用完成後再 plan,既有資源應收斂為
noop,資料列則為skip。 - 可組合:一份文件可以宣告多張表與跨表欄位,規則、檢視和授權再以 ref 指向它們。
注意: IaC 不會因為資源從文件消失就刪除它。刪除必須以
state: "absent"明確宣告,讓破壞性變更保持可見。
system 是狀態命名空間與自動標籤
第一行的 header.system 同時扮演兩個角色:
- 它是伺服器 IaC state 的命名空間。
ref只在這個 system 內代表穩定身分,plan/apply 會用它把宣告與 live resource 對上。 - 非空的 system 會成為受管理表格的 IaC 自動標籤,方便辨認資源由哪個宣告系統管理。
system 最長 100 個字元;空字串是合法值,但不會同步自動標籤。請為長期維護的系統選一個穩定名稱,不要把部署時間或環境產生的 ID 放進去。
JSONL 心智模型
JSONL(newline-delimited JSON)的規則很小,但很嚴格:
- Content-Type 使用
application/x-ndjson;每個非空白實體行只能是一個完整 JSON object。伺服器以 Python 的str.splitlines()切行,所以除了\n、\r\n與\r,U+000B、U+000C、U+001C–U+001E、U+0085、U+2028 與 U+2029 也會結束一行。JSON 允許字串裡直接出現 U+0085、U+2028 與 U+2029,而 export 寫出非 ASCII 文字時不做跳脫,因此含有這些字元的字串值會把所在行切斷,並以invalid JSON失敗;請改寫成跳脫序列\u0085、\u2028與\u2029。 - 文件開頭不要加 byte-order mark。伺服器不會移除它,第一行會以
line 1: invalid JSON: Unexpected UTF-8 BOM (decode using utf-8-sig)失敗。 header可省略;若提供,它必須是第一個非空白行且只能出現一次,版本目前固定為1。省略時 system 是空字串。- 空白行會忽略;
#註解不是 JSON,不能放進文件。 - 後續行依文件順序規劃。先宣告
table,再宣告使用該表的子資源;跨表欄位也應在目標表 ref 可解析後出現。 - 每一行的頂層欄位都是嚴格的:未知或拼錯的 key(例如把
state寫成stat)會在 parse 以extra_forbidden失敗。有型別的部分也一樣——header、table/client_access/command/public_read的 spec,以及 grant 的principal——trigger的 spec 與其 action 也一樣,它們的 key 集合在 parse 時就會被檢查。column、rule、view與grant行的spec,以及 record 的data,在 parse 時是自由格式的 object;它們之後才被檢查,檢查方式各不相同:column 的未知 key 在該行建立 column 時於 apply 失敗,而 view 的頂層 spec、grant 的 spec、既有的 column 與大多數 rule 類型完全不拒絕未知 key。哪個檢查會攔下什麼,請見未知 key。
十二種 line kind 是 header、table、column、rule、trigger、view、grant、client_access、record、command、insight_selection 與 public_read。這也是 discriminated union 宣告它們的順序,而 custom_table_iac_state.resource_type 同樣新增了這三個值。這個 union 另外還宣告三種在本節沒有獨立頁面的 kind:work_calendar(見工作日行事曆),以及 child_collection 與 record_bundle(見子集合)。
三種 scope 都提供經驗證的 GET .../tables/iac/contract authored-contract 路由:
/private/module/custom_tables/chatroom/{chatroom_id}/tables/iac/contract/private/module/custom_tables/department/{department_id}/tables/iac/contract/private/module/custom_tables/company/tables/iac/contract
其 IacDocumentContractResponse 包含 version、kinds、resource_types 與 line。Runtime 的 line 值是一條合法 header;更重要的 OpenAPI 用途,是讓 schema 帶出完整的 discriminated IacLine union——plan/apply 接收的是原始 JSONL bytes,無法自行讓 request model 出現在 OpenAPI。生成工具應讀 contract/OpenAPI schema,但 apply 前仍必須對 live state 執行 plan。
其中三種不遵循其他 kind 共用的 {table}.{ref} 形狀。command 屬於 scope 層級,使用 command:{ref};insight_selection 使用 insight.{chatroom uuid} 且完全沒有 spec;public_read 雖是資料表子資源,但與單例的 client_access 不同,同一張表可以有多個。
Plan action 與 state row 以這個限定形式作為 ref,它必須符合 ^[a-z0-9][a-z0-9_.:-]{0,149}$:最多 150 個字元,也就是 state 表 ref 欄位的寬度。作者端的長度限制確保每個衍生形式都放得進去。作者撰寫的 ref、table 或 renamed_from 必須符合 ^[a-z0-9][a-z0-9_-]{0,63}$——最多 64 個字元,不含 . 或 :——而原始 grant principal id 必須符合 [a-z0-9][a-z0-9_.-]{0,35}:最多 36 個字元(UUID 的長度),而且不能有冒號,因為冒號是 grant:{type}:{id} 各段的分隔符。身分 token 會在建立 grant ref 之前先解析成原始 id,所以 token 的自由文字內容不必符合這些規則。
一個小型系統
下列每一行都是獨立 JSON object;contacts 與 email 是作者選定的穩定 ref,不是伺服器 ID。
{"kind":"header","version":1,"system":"crm","description":"Shared contact workflow"}
{"kind":"table","ref":"contacts","spec":{"name":"Contacts","description":"Managed by JSONL IaC","key":"email"}}
{"kind":"column","table":"contacts","ref":"email","spec":{"name":"Email","type":"text","required":true}}
{"kind":"view","table":"contacts","ref":"directory","spec":{"name":"Directory","is_shared":true,"config":{"columns":["email"]}}}
{"kind":"record","table":"contacts","data":{"email":"alex@example.com"},"on_drift":"skip"}Plan 會先解析這些 ref,再產生 create、adopt、move、update、noop、delete、orphaned、insert 或 skip action。這份文件只是 desired state;真正的 live 差異仍以伺服器 plan 為準。
接著閱讀
- 從標準編寫流程開始,把既有系統先 export,再進行修改。
- 從
header開始,用十二種 line kind 頁面逐欄核對 authored contract,而不是猜測 API response 欄位能否寫回。 - 只要要寫出指名人員、部門、聊天室或社群用戶的行,就先讀可攜身分 token。
- 在送出變更前理解 plan/apply、狀態模型與錯誤階段。
- 欄位與端點的完整契約請查閱 IaC API 參考。
- 完整部署還需要幾個 REST 呼叫:用 REST 補完 列出六項文件裝不了的設定,以及每一項之後由哪一邊擁有。
動手試試
把上面的五行貼到 IaC 工作台。先用本機驗證確認 JSONL 與 ref,再選擇一個測試 chatroom 執行唯讀 plan;第一次看見 diff 時先審查,不要立即 apply。