rule line
rule 把 table 的規則清單拆成可獨立審查、移動與刪除的 IaC resource。IaC ref 提供穩定 identity;apply 會把各 rule lines 合併進通過既有 rules validator 的完整規則集合。
JSONL Line
{"kind":"rule","table":"orders","ref":"unique_order_no","spec":{"type":"unique","name":"Order number is unique","columns":["order_no"]}}欄位契約
頂層對應 IacRuleLine:
| 欄位 | 必填 | 精確契約 |
|---|---|---|
kind | 是 | 固定 "rule" |
table | 是 | parent table ref |
ref | 是 | table 內的穩定 child ref |
state | 否 | present/absent,預設 present |
renamed_from | 否 | 舊 child ref 或 null |
spec | 條件式 | object;present 必填,只有純 rename 可省略;absent 禁止 |
IacRuleLine.spec 在 IaC Pydantic model 中刻意是 Dict[str, Any],所以本機 parse 不假裝有一個不存在的 typed union。伺服器 apply 會交給既有 TableRulesPayload/shared validator;共同 authored 欄位是必填 type,以及可選 name、when 與 type-specific fields。作者不要提供 server-minted id 或 constraint_id;IaC ref 已負責 identity。
type 的精確集合是 compare、unique、unique_tuple、same_dimension_reference、date_interval_exclusion、no_overlap、exists、not_exists、count_limit、transition、check、require、require_approval、channel、invariant、immutable_when。各 type 的必要欄位由 rules validator 決定,例如 unique.columns、unique_tuple.components、compare/check predicates、transition columns、exists/not_exists 的 target/match/where、count_limit 的計數範圍、channel/invariant policy,以及 immutable_when 的 prior_when 與 columns。完整規則 payload 請查閱規則 API。
Ref 與身分規則
Qualified identity 是 {table}.{ref},例如 orders.unique_order_no。同一 table 的 column、rule、trigger、view、public_read refs 與固定的 client_access identity 共用這個空間;任何跨 kind 重名都會被拒絕。
Rule spec 裡位於 column、left、right、start_column、end_column、interval_column、date_column、sort_by、via 的字串,columns/scope_columns 陣列,以及 filters/data object keys,都被視為同 table 的 column refs。Plan 會 fail closed 地翻成 internal keys。這個 walker 與 trigger、view spec 共用,所以單一 ref 的 key 還包括 weekday_column 與 closed_column(materialize_slots 的 source 區塊),以及 row_column、bucket_column、cell_column、lane_column 與 label_column(matrix 與 timeline view)。這些 key 底下的字串,不論出現在 spec 的哪一層,都會被當成 column ref;exists/not_exists 的跨表 target/match 語意仍由 server validator 以 live table 關係核對。unique_tuple rule 不需要特別處理:每個 components[].column 都由同一個 single-ref walker 接手。
same_dimension_reference Rule
same_dimension_reference rule 由專屬的解析器翻譯,因為它三個欄位中有一個屬於另一張表格:
{"kind":"rule","table":"order_lines","ref":"same_company","spec":{"type":"same_dimension_reference","reference_column":"order","source_dimension":"company","target_dimension":"company"}}reference_column與source_dimension是 rule 所屬表格上的 column ref。target_dimension是reference_column所連結表格上的 column ref,不是 rule 所屬表格上的。reference_column與source_dimension必須是本文件宣告、或已由 IaC 管理的link欄位;否則 plan 找不到它們的目標表格。
Plan 會以 SameDimensionReferenceRuleSpec 驗證 spec(因此 when 必須省略或為空,未知 key 會失敗),並解析三個 ref;兩種失敗都是 validate-phase 錯誤(見錯誤目錄)。Apply 會執行共用的 rules validator,它還要求三者都是原生 cardinality-one link、兩個維度 link 指向同一張表格,接著檢查既有的資料列。若已有 live 資料列違反該 rule,該行會以 same_dimension_reference_violation 拒絕而失敗,且不會儲存任何東西。Export 會在目標表格的命名空間中反向翻譯 target_dimension;若該目標表格完全無法匯出,export 會回 400:same_dimension_reference target schema cannot be exported。
對 unique、unique_tuple、same_dimension_reference 與 date_interval_exclusion rule,plan 現在也會對照 last-applied spec 回報 drifted_fields;帶有其中任一種 rule 的文件或被觸及表格,會把 plan hash 綁到該表格的 write-contract digest(見 Plan 與 apply)。參照圖中、或帶有期間 rule 之表格上的 record 行寫入方式也不同,見 record line kind。
由於 differ、executor 與 export 三邊宣告的是同一組 key,immutable_when 的鎖不需要任何特例就能走完整趟:prior_when[].column 由通用的 single-ref walker 接手(column 出現在 spec 任何位置都算 single-ref key,包含巢狀 predicate 清單內),columns 則由 list-ref walker 處理。因此一條鎖能通過 export → plan → apply,並在重新 plan 時是 noop。
Rule spec 也可以帶 $row.{column ref} 樣板 token,export 會寫成 ref,匯入時再翻回內部 key。無法解析的 token 會原樣保留而非 fail closed——詳見可攜身分 token。
date_interval_exclusion Rule
{"kind":"rule","table":"room_bookings","ref":"no_double_booking","spec":{"type":"date_interval_exclusion","start_column":"starts_on","end_column":"ends_on","scope_columns":["room"]}}start_column 與 end_column 是 rule 所屬表格上 date 欄位的 ref,scope_columns 則列出該表格上一到兩個基數為 one 的 link 欄位。通用的 ref walker 會翻譯它們全部,所以它們和其他欄位 ref 一樣被檢查。Plan 會先以 DateIntervalExclusionRuleSpec 驗證 spec(when 必須省略或為空,未知 key 會失敗),並對有問題的行回報一則 invalid date interval rule fields: … validate-phase 錯誤(見錯誤目錄)。Apply 執行共用的 rules validator——它還要求起始欄位必須是 required、結束欄位必須允許 null——接著檢查已存在的資料列:若 live 資料列已違反 rule,該行會以 date_interval_exclusion_invalid_period 或 date_interval_exclusion_overlap 失敗,什麼都不儲存(date_interval_exclusion_retry 表示 apply 等待期間規則所指名的欄位或 link 被改變:重新 plan 並再 apply)。觸及這類 rule 的 plan 會把 plan hash 綁到該表格的 write-contract digest,和 unique、same_dimension_reference rule 相同。rule 本身的說明見 date_interval_exclusion。
period_fence Rule
{"kind":"rule","table":"invoices","ref":"posting_period","spec":{"type":"period_fence","date_column":"posted_on","period":{"table":"periods","start_column":"starts_on","end_column":"ends_on"},"open":[{"column":"open","op":"eq","value":true}]}}Range 模式使用 IaC period.table(table ref),不是 REST 的 period.table_id。date_column 在受保護表;bounds 與 open predicate 欄位則在期間表。可選 period.match 將最多兩個期間表 link ref 對應至來源表 link ref;IaC 必須使用明確 ref,不能是 $same。Link 模式則使用本表 cardinality-one 的 period.link_column。open 必填 1–5 個 AND predicates;require_period 預設 true。每表最多三個 fence,非空 when 會被拒絕。Plan 會驗證並翻譯相依項目;record 行在 apply 仍受 fence 檢查,不能帶 Command 的人類覆寫理由。生命週期檢查與 ct.scope_closed reason 請見期間圍欄。
SCP link leaf 原樣帶過
在 rule 的 policy 子樹內,SCP 的 link_membership 或 link_target leaf 是以形狀辨識——鍵是否存在,加上各自的允許清單,link_membership ⊆ {link, op, value, require_present}、link_target ⊆ {link, target, quantifier, require_present}——接著由 plan、apply 與 export 原樣複製過去。
要記住的結果是:link_target 的 target 子樹指的是被連結表的欄位,永遠不會對 rule 自身的表解析。過去 ref walker 會依位置比對 target 內裸的 column 鍵,產生誤導性的 unknown column ref '<near table>.<linked column>'——或者當近端表剛好有同名欄位時,靜默把錯誤的 key 寫進儲存的 policy。Leaf 自身的 link 仍會在 apply 時驗證,因此原樣帶過不可能寫入無法解析的東西。
這類 leaf 不會被拒絕。它在 export → plan → apply 之間逐位元組保留,並且是透過 REST 編輯——target.column 無法在 IaC 中撰寫或翻譯。
注意: 這條邊界只適用於
policy鍵之下。在 policy 之外,link只是普通的 JSON 鍵——出現在api_call的 body 或 headers、invoke_command的輸入、json欄位的值——而且欄位本身也可以就叫做link,並成為 viewfilters或 triggerdatamap 的鍵。那些文件會被正常走訪,不會被拒絕。
生命週期與規劃
Present line 可規劃 create、adopt、move、update 或 noop。adopt 目前不會收斂:apply 會以 rule '{table}.{ref}': adopt with no resolvable native id 讓該行失敗,下一次仍規劃 adopt;請先透過 REST 移除 live 的那一份,讓該行規劃 create(見 adoption)。renamed_from 讓同一 stored rule 換 ref;state: "absent" 規劃從完整 rules 集合移除該 rule。Apply 走 validated PUT path,會保留文件未管理的其他 rules,而不是用單一 line 覆蓋整張表。
Rule semantics 可能建立資料庫 constraint、阻擋既有資料或影響 approval/channel policy。Local parser 與 plan 會先驗證文件內可見 refs、live diff 與集合 cap;free-dict 的 type-specific shared validator 仍在 apply 的持久化路徑執行,因此 apply result 可能再出現 per-line error。
驗證錯誤
Lifecycle、parent 與跨 kind collision:
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.unique_order_no' (already declared in this document)若 columns: ["missing"] 無法解析:
unknown column ref 'orders.missing'若合併後的 rule 集合會撐破每表 10 條的上限:
rule cap exceeded (12 > 10); unmanaged rules occupying slots: [names]前兩類 lifecycle detail 來自丟出一般 ValueError 的 validator:伺服器在 plan 與 apply 都以 parse 階段的 400 回應,detail 是 loc 為 ["rule"] 的 typed value_error issue(見 Validator 訊息類別),工作台顯示相同的 detail;type-specific rules errors 來自 apply 的 downstream shared validator,不屬於工作台 byte-exact 字串。精確本機字串來源:components/iac/parse.ts。
動手試試
在 IaC 工作台宣告 table、order_no column 與範例 unique rule,把 column ref 改成未知值以確認 validate error。修正後對測試 scope plan,再到規則 API核對 type-specific contract。
aggregate_limit 參照
aggregate_limit 的 group_by、guard_partitions、sum column、$row bound 與 linked-bound 欄位使用 column ref;linked bound 透過 group link 解析目標。guard_partitions 必須等於同順序的分組 key。Plan/apply/export 共用轉譯;policy admission 與預覽包含現有列。Deferred 在原子 Command 或暫存世代啟用的最終狀態檢查,其他寫入仍立即檢查。