Skip to Content

IaC 狀態模型

IaC state 是伺服器保存的 ref 對應關係與上次套用意圖。它讓人類可讀的 contacts.email 在多次 plan/apply 之間仍指向同一個 table/column resource,也讓規劃器分辨「宣告真的改了」與「live 值在宣告之外漂移了」。

State 儲存什麼

每個受管理資源的 state 會記錄 scope、system、resource type、qualified ref、live resource ID、table ID,以及 last-applied spec。對外 state endpoint 刻意只回傳識別與時間資訊:

{ "system": "crm", "resource_type": "column", "ref": "contacts.email", "resource_id": "22222222-2222-4222-8222-222222222222:col_1a2b3c", "table_id": "22222222-2222-4222-8222-222222222222", "updated_at": "2026-07-19T08:30:00Z" }

resource_type 是十個值的封閉集合:tablecolumnruletriggerviewgrantclient_accesscommandinsight_selectionpublic_readheaderrecord 永遠不會出現——header 不宣告資源,record 則完全不寫 state。

最新的三個值並非都像資料表子資源:

resource_typeref 形式resource_idtable_id
commandcommand:{ref}Command UUIDCommand 自身的綁定,不是父資源
insight_selectioninsight.{chatroom uuid}insight_selection:{tag_id}:{chatroom_id}null
public_read{table}.{ref}Token UUID父資料表

last_applied_spec 是伺服器內部 diff 基準,不由 state endpoint 暴露,也不是 authored JSONL。Secrets 會被移除;只記錄宣告實際提供的 keys。

三方 Diff

Plan 同時比較:

  1. desired:本次 JSONL 的 spec
  2. last applied:state 記住的上次 authored fields;
  3. live:目前 API resource 的值。

Desired 相對 last applied 的變化是新意圖;live 相對 last applied 的變化是 drift。欄位只有在宣告提供時才由 IaC 擁有。從 spec 省略欄位會保留 live 值並放棄管理,不會把它重設為 model default。

Qualified Ref 與唯一 Claim

Table ref 是 system 內的 key,例如 contacts。子資源共用一個 qualified-ref 空間,格式是 {table}.{ref},例如 contacts.email。Column、rule、trigger、view、public_read 與固定的 client_access identity 不能在同一張表重用相同 ref,即使 kind 不同;client_access 一律 claim {table}.client_access,plan 必須能從一個 token 唯一解析出資源。

有兩種 kind 位於這個空間之外。Command 屬於 scope 層級,使用保留形式 command:{ref};insight selection 使用 insight.{chatroom uuid}。兩者仍會以各自的完整 qualified 字串參與文件層級的重複 ref 檢查,因此兩行 command 宣告同一個 slug 會碰撞。以 . 切割 state ref 來推導父資料表的工具,在這兩者上都會出錯。

Persistent state 的唯一鍵包含 (resource_type, ref);但 planner 對同一 scope 與 system 載入的 map 只以 ref 為鍵,因為目前文件會強制所有 kind 共用一個 qualified-ref namespace。不同 resource type 的舊 state 列若撞 ref,就是需要清理的損壞狀態:載入時會警告並只保留其中一列;各 kind 在操作前也會檢查 resource_type,不相符的 survivor 會被忽略,而不會被錯當成另一種 resource。不要依賴哪一列剛好被保留。

同一 live resource 不能同時被兩個 system claim。若要改名,使用 renamed_from 產生 move;若要交給另一個 system,先以明確流程解除舊 claim,再由新 system 採用,不能讓兩份文件競爭同一 ID。

Missing、Orphaned 與 Absent 不同

  • 文件中 missing:省略一行不刪除 live resource,也不保證刪除 state;未宣告資源保持原狀。
  • orphaned action:state ref 還存在,但它指向的 live resource 已不存在。這是需要修復或清理的狀態,不等同宣告刪除。
  • state: "absent":作者明確要求刪除或撤銷,plan 才會產生 delete。Table 可帶 spec 先採用再刪除;其餘七種可帶 spec 的具狀態 kind,在 absent 時都不能帶 spec。

Apply 結束時會清理孤兒 state 列,而且被清掉的 ref 會在 tag 同步之前從記憶體中的 state 移除。隱含的 system tag 種子與 apply 結束時的 tag 同步,都會跳過那些資料表已不存在的 state 列。這個順序正是為了避免「在別處被刪掉的表把死掉的 ID 餵進 tag 建立流程」——過去這會讓第一次 apply 以 tag 同步錯誤失敗,要跑第二次才會自癒。

已撤銷、且未被宣告的 public_read state 列不會解析為 live,會被同一輪清理掉,這也是撤銷後那些列消失的原因。

Export 不會複製 State

Export 是純讀取,也不建立 state row。它會優先重用 scope 裡已有的 refs,替未管理的 live resources 產生決定性 ref,並輸出可重新匯入的 JSONL。輸出攜帶的是 authored identity(system 與 refs),不是 state database 的內部列。

因此,export 文件可以跨 scope 使用:在新 scope plan 時,refs 解析為要建立或採用的 resources;apply 成功後,伺服器才為新 scope 建立自己的 state。

Export → Apply → State Round-trip

  1. 從現有 scope export system: "crm"
  2. 不改 authored refs,把文件拿到全新的 scope plan;預期資源為 create、資料列為 insert
  3. Apply 已審查 hash;伺服器建立 resources 並把 refs 寫進該 scope 的 state。
  4. 對同一文件再 plan;資源應為 noop、資料列應為 skip
  5. 查詢 state,確認 contactscontacts.email 等 refs 綁到新 scope 的 IDs,而不是來源 scope IDs。

這個 round-trip 證明 portable 的是宣告與 refs;resource IDs 與 state 永遠由目的 scope 擁有。

伺服器擁有欄位

settings.column_mapping 是 display name 到 internal column key 的伺服器映射;settings.iac 是 IaC 管理 metadata。兩者不能出現在 table authored spec,會得到:

settings.column_mapping is server-owned and cannot be set via IaC settings.iac is server-owned and cannot be set via IaC

Export 也會移除 column_mappingiac 與嵌在 table settings 的 client_access。請以 column ref 表達欄位關係;client_access 使用自己的 line kind。伺服器會在建立/更新欄位時維護 mapping,作者不得把某個環境的 internal keys 當成 portable state。

檢視 State

使用 GET .../tables/iac/state?system=crm 檢查 ref 綁定。State 回應適合診斷 identity,不是完整資源 export;要重建可編輯文件仍應呼叫 export。端點欄位請見 state 參考

動手試試

在測試 scope export 一個 system,把文件 plan/apply 到另一個空 scope,然後比較兩邊的 state resource IDs。最後對目的 scope 再 plan,確認所有受管理資源已收斂為 noop

Last updated on