Skip to Content
核心概念JSONL IaCJSONL IaC 概念

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 同時扮演兩個角色:

  1. 它是伺服器 IaC state 的命名空間。ref 只在這個 system 內代表穩定身分,plan/apply 會用它把宣告與 live resource 對上。
  2. 非空的 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。

Last updated on