Skip to Content

IaC 錯誤目錄

IaC 錯誤先按階段處理:parse 決定每個實體行能否成為合法、符合 kind schema 的 object;validate 再跨行解析 refs 與語意關係。修正前一階段後才處理下一階段,能避免被連鎖錯誤誤導。

錯誤形狀與階段

除了下方另述的同步 parse abort,伺服器的 per-line 錯誤 envelope 是 {line, kind, ref, phase, code, detail}。

  • line 是原始 JSONL 的 1-based 實體行號,包含空白行——但 0 是合法值,代表「與任何文件行無關」。
  • phase 是 parse、validate、diff 或 apply 其中之一。
  • code 是選填的穩定 enum;本頁記載 iac_plan_stale、ct.iac_time_budget_exceeded(Request 時間預算)、retryable_lock_conflict、invalid_moderator_target、governed_context_required、unique_tuple_conflict(被 unique_tuple rule 拒絕的寫入:record 行、在既有重複值上新增或變更這類 rule 的 rule 行,或轉換後的值會互相碰撞的 column 型別變更;per-line apply 錯誤)、date_interval_exclusion_invalid_period、date_interval_exclusion_overlap、date_interval_exclusion_retry、approval_staging_unavailable、effect_identity_locked、trigger_secret_keep_refused、command_output_reserved(command 行宣告了名為 approval_provenance 的 output;validate 階段錯誤,見 approval provenance)、iac_permission_denied(呼叫者不得使用的資源;403 body 中唯一那一筆的 code,見文件層級的狀態碼),以及十二個日曆/sequence code。此 enum 的值比本頁列出的更多,所以 line error 可能帶有本頁未記載的 code;沒有任何 code 能歸類的舊有 diagnostics 不設值。

工作台錯誤清單顯示 {line, ref, phase, detail}。ref 為空時,顯示的識別值依序改用 kind、JSONL;kind 不是另外顯示的欄位。完整伺服器 envelope 請見 plan 參考。

Phase 決定一個錯誤造成多大破壞。Request-time parse abort 什麼都不執行,並回 flat-detail 400;validate 錯誤同樣不執行,但以 422 回傳 plan 的完整錯誤清單——同一份 plan 的 diff 階段項目也會一併帶上,每一項都有自己的 code——除非更早的 supplied-plan_hash mismatch 已先產生 409 plan_stale。Parse、validation 與 hash 檢查都在 asynchronous dispatch 前執行,所以這些 request-time failure 都不會產生 ticket。diff 階段錯誤不會阻擋 applyability:同步 apply 回傳 200,只略過該行,再繼續處理其他可執行的行。apply 階段錯誤同樣位於成功 apply result 的 per-line 錯誤中;失敗的行會回滾自己的 transaction,然後繼續執行。唯一的例外是因驗證與日期期間 rule 以外的原因被拒絕的基數為 many 的 link cell:它會在先前各行已 commit 之後,以該次寫入自己的 HTTP 錯誤讓整個 request 失敗。大型文件通過 preflight 並拿到 202 後,worker 會強制重新 parse 與 plan 已暫存的 bytes;只有這個 worker-side recheck 的失敗才會變成下方所述的 terminal parse_error 或 validate_error ticket。

以下 code block 的 detail 是 byte-exact 形式。{name} 代表錯誤當下插入的實值,不是回應裡會保留的大括號。已驗證的本機字串來源是 components/iac/parse.ts。

Parse 階段

文件與 JSON

document exceeds {max_bytes} bytes document is not valid UTF-8 excessively nested JSON structure (max depth 128) invalid JSON: {detail} header must be the first line and unique record cap exceeded for table '{table}' ({max_records}) line cap exceeded ({max_lines}, record lines exempt) table cap exceeded ({max_tables})

四個上限都是部署層級的可調參數:

訊息參數預設值計算對象
document exceeds {max_bytes} bytesCUSTOM_TABLE_IAC_MAX_BYTES5 MiB(5,242,880 bytes)原始 request body
line cap exceeded ({max_lines}, record lines exempt)CUSTOM_TABLE_IAC_MAX_LINES2,000非 record 行;header 與空白行不計入
table cap exceeded ({max_tables})CUSTOM_TABLE_IAC_MAX_TABLES40table 行
record cap exceeded for table '{table}' ({max_records})CUSTOM_TABLE_IAC_MAX_RECORDS200單一表格的 record 行

CUSTOM_TABLE_IAC_SYNC_RECORD_LIMIT(預設 200)不是上限:整份文件的 record 行總數超過它時,apply 會回 202 並以背景 ticket 執行;重量級欄位變更也以同樣方式改走 ticket;自後端 5.22.0 起,plan 建立、更新、刪除或搬移的資源超過 CUSTOM_TABLE_IAC_SYNC_CHANGE_LIMIT(預設 300,見變更量大的 plan)時亦然。Export 會對自己的輸出套用 table、line 與 byte 上限(見下方),並把 include_records 限制在每張表格 CUSTOM_TABLE_IAC_MAX_RECORDS 列。

這個順序就是實際檢查順序,而且看得出來。byte 上限與 UTF-8 解碼屬於文件層級,在進入逐行迴圈前只跑一次;其餘全部逐行執行,而巢狀深度守衛是第一個逐行檢查——排在 json.loads 之前、line kind schema 驗證之前、header 規則之前,也在三個 cap 之前。因此一行同時「過深」且「JSON 壞掉」時,回報的是 excessively nested JSON structure (max depth 128),不會是 invalid JSON: {detail};一行同時過深且會撞上 record cap exceeded、line cap exceeded、table cap exceeded 或 header must be the first line and unique 時,回報的也是巢狀中止,不是那些 cap。

深度上限固定為 128,不是 CUSTOM_TABLE_IAC_MAX_* 那組環境變數之一,各部署無法調高。它計算的是整個實體行上的 [ 與 {,包含 line envelope:record line 最外層的 { 是 depth 1、其 data object 是 depth 2,所以 record payload 實際可用約 125 層。字串字面值內的括號不計入,反斜線跳脫也會被正確處理。比較是嚴格大於——量到 128 通過,129 中止——而且每一行各自判定,因此 500 行淺文件完全不受影響。這個預算刻意留有餘裕:最深的合法 payload 是 command definition,它自己的 JSON 深度上限是 64。

在這個守衛存在之前,唯一的巢狀保護是 json.loads 外層的 RecursionError catch,而現代 CPython 根本不會觸發它。於是一顆 nesting bomb 會乾淨地 parse 完、通過 schema 驗證,並一路進入 diff 與 plan——被 deep copy、重新序列化、重建成 Pydantic model——之後才可能碰到較窄的限制,而且只在 json 欄位上(MAX_JSON_CELL_DEPTH 32)。

工作台本身沒有深度上限:巢狀達 129 層或更深的行在本機會通過,直到它的遞迴 parser 發生堆疊溢位,才會輸出 invalid JSON: excessively nested JSON structure——有前綴、沒有深度,而且來自 RangeError 而非量測出來的上限。只有伺服器會強制這個上限,也只有伺服器端的字串帶 (max depth 128)。工作台也會重現 JSON decoder 的 detail,例如 invalid JSON: Expecting property name enclosed in double quotes。伺服器另負責 byte/UTF-8 與部署上限;本機驗證不能取代伺服器端的上限。

這一族已經沒有「feature flag 關閉」的分支。CUSTOM_TABLE_IAC_V14 已刪除,因此 plan、apply、state 與 export 無條件可用。除了上面那些上限,parse 階段只會對下列情況產生 400:行未通過它的 kind schema、未通過它的某個 validator(value_error),或未知 key 檢查(extra_forbidden),全都在下方說明。另外有兩個 request body 的 400 位於端點層:

Provide the JSONL document as a 'file' multipart part or as the raw request body Empty document: provide the JSONL as a 'file' multipart part or as the raw request body

空文件檢查只適用於原始 body。空的 file part 不會被拒絕:它會被解析成沒有任何行的文件,所以 plan 回傳空的 plan,apply 則回 200 且什麼都不變更。

Request-time parse 中止的格式是 line {n}: {detail},而且——與本頁其他所有錯誤不同——它是以扁平的 400 detail 字串送出,不是 {line, kind, ref, phase, code, detail} envelope:parse 是全有全無,沒有 per-line 清單可以承載。文件層級的中止使用 line 0。這個回應發生在 dispatch 前,絕不會產生 async ticket。若文件已經拿到 202,之後在 worker 強制重新 parse 已暫存 bytes 時才失敗,poll wrapper 會改帶 body.status: "failed"、body.error.code: "parse_error",以及 body.error.message: "line {n}: {detail}"。Worker-side replan 若已不 applyable,則使用 body.error.code: "validate_error" 並附上 per-line errors。這些是防禦性 worker recheck,不是 caller 原始 malformed 或 invalid request 的另一種回應。

Export 另有自己的 400。它寧可拒絕,也不輸出無法重新匯入的文件:

table '{table_id}' not found in this scope managed table '{table_id}' not found in this scope export would produce {n} table lines, exceeding CUSTOM_TABLE_IAC_MAX_TABLES ({max}); narrow the export with table_ids= export would produce {n} non-record lines, exceeding CUSTOM_TABLE_IAC_MAX_LINES ({max}); narrow the export with table_ids= or set include_records=false export would produce a {bytes}-byte document, exceeding CUSTOM_TABLE_IAC_MAX_BYTES ({max}); narrow the export with table_ids= or set include_records=false export: column '{internal key}' targets table '{table_id}' which no longer resolves live (deleted) — cannot emit a ref export: column '{internal key}' sources table '{table_id}' which no longer resolves live (deleted) — cannot emit a ref export: column '{internal key}' falls back to table '{table_id}' which no longer resolves live (deleted) — cannot emit a ref export: materialize_slots targets table '{table_id}' which no longer resolves live (deleted) — cannot emit a ref export: internal key '{internal key}' has no ref mapping (legacy/unmanaged column) same_dimension_reference target schema cannot be exported

第一則回應不是此 scope live 表格的明確 table_ids 項目。第二則回應 system=:該 system 的 state row 或標籤指派仍指向已消失或已進垃圾桶的表格。行數上限也把 insight_selection、public_read 與 command 行算進去。

Pydantic 欄位契約

缺少必填欄位、literal/enum 不符、型別錯誤、pattern/長度違規與未知欄位,會用後端 parser 的 JSON-serialized Pydantic issue list 呈現。伺服器本身最多保留前三個 issues,並省略每個 issue 的 input 與 ctx(所以一個 issue 就是 {type, loc, msg, url});工作台也一樣。缺少 header version 的精確 detail 是:

[{"type": "missing", "loc": ["header", "version"], "msg": "Field required", "url": "https://errors.pydantic.dev/<pydantic-version>/v/missing"}]

record.on_drift: "merge" 與 client_access.spec.passphrase_enabled: "maybe" 的精確例子:

[{"type": "literal_error", "loc": ["record", "on_drift"], "msg": "Input should be 'skip' or 'update'", "url": "https://errors.pydantic.dev/<pydantic-version>/v/literal_error"}]
[{"type": "bool_parsing", "loc": ["client_access", "spec", "passphrase_enabled"], "msg": "Input should be a valid boolean, unable to interpret input", "url": "https://errors.pydantic.dev/<pydantic-version>/v/bool_parsing"}]

<pydantic-version> 代表執行中 image 所安裝的 Pydantic 次版本(例如 2.12)。後端並未固定 Pydantic 版本,所以請比對 type、loc 與 msg,不要比對 URL。

client_access.spec.passphrase 可省略或為 null,都代表不輪替。明文必須是 1–72 個 UTF-8 位元組;"<REDACTED>" 要求保留既有 hash。具型別字串欄位先限制 1–72 個字元:空字串是 string_too_short,超過 72 字元是 string_too_long。字元數符合限制、但超過 72 個 UTF-8 位元組,才會產生 value_error,其 msg 為:

passphrase must be at most 72 UTF-8 bytes

字串欄位通常以 string_unicode 先拒絕孤立 surrogate,不會走到位元組 helper 的 passphrase must be valid UTF-8;最小長度也先攔下 passphrase must not be empty。Plan/apply 保留防禦性的 passphrase: {message} 位元組複查,不是 typed parse 成功後,一般 JSONL 額外可能遇到的失敗路徑。

passphrase_enabled with '<REDACTED>' requires an existing live passphrase hash 也屬於 validate:plan 在既有表、enabled 為 true、sentinel 沒有既有 hash 時檢查;要新建的表則先收到 client-access warning。非 noop apply 會在鎖內複查,即使 enabled 為 false,沒有 hash 仍拒絕 sentinel;同一訊息仍保留在 apply 目錄。

Validate 階段

Ref、順序與碰撞

duplicate table ref '{ref}' duplicate ref '{table}.{ref}' (already declared in this document) unknown table ref '{table}' unknown table ref '{table}' (target) unknown table ref '{table}' (source) unknown column ref '{table}.{ref}' unknown column ref '{table}.{ref}' (link_field) unknown column ref '{table}.{ref}' (target_column) unknown column ref '{table}.{ref}' (match) unknown column ref '{table}.{ref}' (filter) unknown column ref '{table}.{ref}' (expression) unknown column ref '{table}.{ref}' (pick.order_column) unknown table ref '{table}' (fallback.table) unknown column ref '{table}.{ref}' (fallback.target_column) unknown column ref '{table}.{ref}' (fallback.match) invalid formula expression: {detail} column '{table}.{ref}' resolves to a legacy (non-col_-prefixed) internal key; not addressable by IaC formula translation

fallback.match 那一則的 {table},對 match key 而言是 defaults 表格,對 $row.<ref> 值而言是該行自己的表格。Legacy-key 那一則出現在公式的 [link -> target] 跳轉解析到一個內部鍵不以 col_ 開頭的 adopt 欄位時;翻譯這種公式可能代換成錯誤的鍵,因此直接拒絕。

column、rule、trigger、view、public_read 與單例 client_access(qualified ref 為 {table}.client_access) 在同一 table 共用 qualified-ref 空間,因此跨 kind 重名也會命中 duplicate ref。command ref 以自己的完整 qualified 字串碰撞,重複的 insight selection 則有專屬訊息:

duplicate ref 'command:{ref}' (already declared in this document) duplicate insight_selection line for chatroom '{uuid}'

工作台的 ref 驗證來源是 components/iac/parse.ts。

Grant

unresolved visible_columns ref '{token}': unknown column ref '{table}.{token}' unresolved read_filter ref '{token}': unknown column ref '{table}.{token}' unresolved edit_filter ref '{token}': unknown column ref '{table}.{token}' can_read "filtered" requires a read_filter read_filter is only valid when can_read is "filtered" can_edit "filtered" requires an edit_filter edit_filter is only valid when can_edit is "filtered" row policy {path} must be one of {"and": [nodes]}, {"or": [nodes]}, {"not": node}, a {"column", "op", "value"} predicate or a {"link", "quantifier", "target"} link_target leaf row policy {path} "{and|or}" must be a non-empty list of nodes row policy {path} "not" takes exactly one node, not a list row policy {path}: {kind} node has unknown key(s): {keys} row policy {path}: link_membership leaves are not supported in row policies — use link_target with a target {"column": "id", "op": "in", "value": [...]} row policy {path}: quantifier must be "any" or "all" row policy {path}: require_present must be stated explicitly (true/false) on quantifier "all" — all([]) is vacuously true row policy {path}: require_present does not apply to quantifier "any" (already false when there are no linked records) row policy {path}: "link" must name a link column (internal col_<hex> key) row policy {path}: a link leaf may not appear inside a link_target's target (row policies are 1-hop) row policy {path} nests deeper than the max 5 levels row policy {path}: require_present must be a boolean row policy {path}: link_target requires a single target node row policy exceeds max 24 predicates ({n}) row policy exceeds max 6 link leaves ({n}) visible_columns references unknown columns: [{keys}]

上方 row policy … 項目是工作台本機結構診斷,以及共用 ACL 文法驗證器使用的形式,不是每種 IaC 伺服器首先回報錯誤的逐位元組目錄。Ref 翻譯先執行,可能以不同訊息拒絕節點,或先重建群組再交給後續驗證。

目標表格已存在時,grant 翻譯以 {read_filter|edit_filter} {path|root}: {message} 回報結構錯誤;public_read 使用 read_filter {path|root}: {message}。根節點是字面值 root,巢狀路徑如 and[0].not。例如 read_filter: {"not":[]} 回報 read_filter not: node must be one of … (got list),不是工作台的 row policy "not" takes exactly one node, not a list。缺少 column/link 名稱及不支援的 link leaf 也使用 filter/path 前綴;另一個 target 內再有 link target 時可回報 link_target is not supported here。翻譯後,grant plan 執行共用 ACL 驗證,public-read 的 mint 驗證則在 apply 執行;其餘文法、上限、operator、token 與欄位型別錯誤仍可能使用 row policy …。

對 present grant,server plan 只有在目標已解析到既有資料表(含 plan 採用)時,才翻譯 visible_columns、read_filter、edit_filter 並驗證 grant 合法性。要由 apply 建立的表改為警告 grant target table is itself being created in this apply; grant legality cannot be fully verified until then;翻譯及完整合法性延至 apply。Parse 欄位檢查、可攜主體 token 解析與未知 table ref 檢查不延後。既有表的驗證器也可回報:

Users not found: ['{user id}'] Users not in the same company: ['{user id}'] Cannot set explicit permissions for table managers — they have full implicit access Department not found Social media client not found audience must be 'internal' or 'external', got '{audience}' Chatroom grants are only valid on department-scoped tables Chatroom not found

精確來源:components/iac/parse.ts。

身分 Token

所有接受可攜身分 token 的介面共用的語法錯誤。括號裡的 {hint} 是該通道自己的文法:cell 通道——record cell、trigger when 值、invoke_command 輸入、link 自然鍵,以及 table.spec.moderators——的內容是 expected '$user:<username>', '$smc:<platform>:<platform_user_id>', '$room:<chatroom name>' or '$room:<department name>/<chatroom name>';grant 通道——grant.principal.id 與 insight_selection.chatroom——會在 username 那一項之後插入 '$dept:<department name>',而 empty department name 也只會出現在那裡。

malformed principal token '{token}': empty username|empty chatroom name|needs platform AND platform_user_id ({hint}) malformed principal token '{token}': empty department name (expected '$user:<username>', '$dept:<department name>', '$smc:<platform>:<platform_user_id>', '$room:<chatroom name>' or '$room:<department name>/<chatroom name>') unrecognized principal token '{token}' ({hint}) principal token '{token}' does not match the column's type: a {kind} column expects {prefixes} or a raw id unknown principal kind '{kind}' for token '{token}'

{prefixes} 是該 kind 自己的前綴集合,以 or 串接:user 欄位是 $user:<...>、social_client 是 $smc:<...>、principal 是 $user:<...> or $smc:<...> or $room:<...>,grant 則是每種型別一個前綴。這道閘門在任何查詢之前執行,所以 token 帶的名稱不必存在;而因為 $dept: 只用於 grant,任何 cell 裡的 $dept: 得到的正是這一則錯誤。

解析錯誤:

principal token '{token}': no user with username '{name}' in this company principal token '{token}': user '{name}' is deleted principal token '{token}': no department named '{name}' in this company principal token '{token}': department '{name}' is deleted principal token '{token}': no live chatroom named '{name}' in this company principal token '{token}': ambiguous — {n} live chatrooms are named '{name}' ({ids}); qualify it as '$room:<department name>/{name}' principal token '{token}': no live chatroom named '{room}' in department '{dept}' in this company principal token '{token}': ambiguous — {n} live chatrooms match ({ids}) principal token '{token}': no social client with platform user id '{id}' on platform '{p}' in this company principal token '{token}': ambiguous — {n} social clients match, in chatrooms {ids} cannot resolve this scope's company for principal token resolution principal token '{token}': ambiguous — {n} departments share the name '{name}' principal token '{token}' was not resolved principal token '{token}' could not be resolved trigger when column '{internal key}': {token error} invoke_command input '{name}': {token error}

trigger when column 以欄位的內部 col_<hex> 鍵稱呼欄位,而不是 ref,因為 token 是在 ref 翻譯之後才替換的。

Token 無法解析的行會以 {prefix}unresolved-{8 個十六進位字元} 形式的佔位 ref 回報,因為 IacPlanAction.ref 有 pattern 限制且不接受 $。它是錯誤標籤,不是資源身分。

Trigger 的 Command 與資料表 Ref

Trigger 的 invoke_command ref 與 materialize_slots 目標 ref 在 plan 時解析:

invoke_command command ref '{ref}': managed command no longer resolves live in this scope invoke_command command ref '{ref}': ambiguous — {n} systems bind this ref to different live commands invoke_command: unknown command ref '{ref}' invoke_command command ref '{ref}': ambiguous — {n} live commands match materialize_slots target.table_id: {detail} materialize_slots target: {detail}

同一份文件中的 command 行不算錯誤:trigger 行會在 apply 時等它套用。這些檢查和上面的身分 token 一樣,只有在 trigger 的資料表是 live、且其 column ref 解析得到時才會在 plan 執行;否則改在 apply 執行,並以 trigger '{table}.{ref}': 前綴讓該行失敗。解析順序請見 trigger line kind。

Table Moderators

table 行的 moderators 必須指向文件所屬公司中仍存在的真人使用者。每一種失敗都是 validate-phase 錯誤,並帶 code: "invalid_moderator_target":

moderators not found, deleted, or outside this table's company service_account_managed_by_department principal token '{token}' was not resolved principal token '{token}': no user with username '{name}' in this company principal token '{token}': user '{name}' is deleted

service_account_managed_by_department 代表其中一個 id 是部門服務帳號;服務帳號不能擔任 table moderator。Apply 會重新執行同一項檢查;若使用者在規劃後有變,會以 apply-phase 錯誤回報這些字串,代碼相同。

decimal 欄位

precision/scale/allow_negative are only valid on decimal columns decimal precision must be an integer from 1 to 38 decimal scale must be an integer from 0 to min(18, precision) decimal allow_negative must be a boolean decimal columns cannot set max_length or options

decimal 欄位的 default_value 以與 decimal cell 相同的規則檢查(見 Record)。以上都是 column 行上的 validate-phase 錯誤。

same_dimension_reference Rule

invalid reference rule fields: {fields} unknown column ref '{table}.{ref}' link column '{table}.{ref}' has no known target table (not registered via add_column(..., target_table_ref=...))

{fields} 會依名稱排序列出有問題的 SameDimensionReferenceRuleSpec 欄位,以逗號分隔,例如 target_dimension 或 when。reference_column 與 source_dimension 是 rule 所屬表格上的 ref,target_dimension 則是 reference_column 所連結表格上的 ref。兩個 link 欄位都必須在本文件以 link column 行宣告,或已由 IaC 管理,否則會出現第三則訊息。其餘檢查在 apply 持久化 rule 時進行:三者都必須是原生 cardinality-one link,而且兩個維度 link 必須指向同一張表格。

date_interval_exclusion Rule

invalid date interval rule fields: {fields} unknown column ref '{table}.{ref}'

{fields} 會依名稱排序列出有問題的 DateIntervalExclusionRuleSpec 欄位,以逗號分隔,例如 scope_columns、scope_columns.0(不是字串的項目)、start_column、when(這個 rule 不接受 when)、id(不符合 rule_<8–32 hex>)、name(超過 128 個字元),或 min_gap 這類未知 key。出現這類欄位的行只會回報這一則錯誤。start_column、end_column 與每個 scope_columns 項目都是 rule 所屬表格上的 ref。其餘檢查在 apply 持久化 rule 時進行,並把第一個失敗回報為該行的 apply 錯誤:start and end columns must be distinct、date_interval_exclusion start column must be required、date_interval_exclusion end column must allow null、date_interval_exclusion requires cardinality-one links、date_interval_exclusion scope columns must be distinct、column '{name}' is type {type} — start_column requires one of: date(end_column 同理)與 '{key}' must be a link column of '{table}'(引號內是欄位的內部 col_<hex> key,因為 apply 會先翻譯 ref)。用 link、rollup、lookup 或 formula 欄位當作邊界時,訊息則是 column '{name}' is type {type} — rules may only reference stored scalar columns (and link columns where documented);這些訊息後面都可能接著 . 與候選欄位提示。表格已有資料列時,同一次 apply 也會拒絕被這些資料列違反的 rule,訊息是下方 Apply 階段列出的 Date period … 字串之一。

Trigger Secret

以下每一則都是 validate-phase 錯誤,並帶 code: "trigger_secret_keep_refused"。第一則照原樣回報;其餘(以 … 標示)會加上 trigger '{table}.{ref}' 前綴,若是單一 action 出錯,後面再接 action {n}(從 1 起算):

{n} trigger secret value(s) are the masked placeholder '__SECRET__', but this line creates the trigger here, so there is no stored value to keep — replace every placeholder with the real value … it keeps a stored secret ('__SECRET__'), but no stored action of this trigger can supply it — a new trigger or action, an action moved from another trigger, or … … it keeps a stored secret (placeholder or an omitted slot), but several stored actions of this trigger could be its twin — send its action id, or the real values … the url's scheme, host or port, or its Host header, changed, so its stored secrets cannot be kept — re-enter every header, query, userinfo and body value for the new destination (or clear them) … header '{name}' is sent more than once (header names are case-insensitive)

訊息只會點名 slot——header 名稱、query key、body path——絕不包含值。保留、取代與清除的規則見唯寫的 trigger secret。

Computed 欄位

only name/description can be updated on {link|rollup|lookup|formula} columns (got: {sorted fields}); to change computed config delete and recreate the column (state: absent + new line) unresolved ref '{token}': {reason}

第一則是 diff 階段錯誤。它出現在 plan action 上,apply 時整份文件回傳 200 且只略過那一行,而不是文件層級的 422。

第二則是 update 行與 live 欄位比對時,該行的跨表 ref 翻譯失敗。它只出現在該行自己的 action errors,不在 plan 的 errors[] 中。Create 行在 plan 時不會翻譯,所以同樣的失敗會在 apply 時以 line {n}: unresolved ref '{token}': {reason} 出現。除了 unknown-ref 訊息之外,{reason} 為下列之一:

pick.order_column requires link_field (picked lookup) to resolve its owning table target_column requires link_field (outgoing rollup/lookup) or source (incoming rollup) to resolve its owning table match requires source to resolve the source table filter requires link_field (outgoing) or source (incoming) fallback requires a `table` ref naming the defaults table fallback requires target_column fallback has unsupported field(s): [{fields}] invalid formula expression: {detail} column ref uses the reserved 'col_' internal-key prefix — would collide with the col_<hex> namespace produced by cross-ref rewriting; rename the ref

最後一則表示:公式中使用的欄位 ref 不能以 col_ 開頭。

在 rule 的 policy 子樹內,SCP 的 link_membership 或 link_target leaf 會以形狀辨識,並由 plan、apply 與 export 原樣帶過。link_target 的 target 子樹指的是被連結表的欄位,永不對 rule 自身的表解析。

這裡沒有對應的錯誤。中途某個版本曾以 unresolved ref '<link>': SCP link leaves (link_membership/link_target) are not yet translatable through IaC — author or edit this rule's policy over REST 拒絕這類 leaf;那則訊息從未成為最終行為,也不應被寫進文件。這條規則的範圍也很窄:在 policy 鍵之外,link 只是普通的 JSON 鍵,欄位也可以就叫做 link。

table settings audience

settings.default_permissions.audience "company" is only valid on department-scoped tables settings.default_permissions.audience must be "scope" or "company", got '{value}'

兩者對任何非 state: "absent" 且帶 spec 的 table line 觸發。它們屬於 validate 階段,會擋掉 applyability,apply 回 422 且什麼都不執行。Apply 端會對live 資料表自身的 scope 重跑同一道檢查,因此直接呼叫或使用已 stale、不再與 live state 相符的 plan 都繞不過去;在那裡同一則訊息會變成該行在 200 內的 apply 錯誤。詳見 table line kind。

insight_selection

insight_selection is only valid for department-scope documents insight_selection requires a non-empty system (header.system) duplicate insight_selection line for chatroom '{uuid}'

public_read

public_read v1 supports secretless tokens only; declare secretless: true (a Bearer-secreted token cannot be minted through IaC — use the REST mint lane) view ref '{view}' is not a managed view in this document (declare a `view` line for it, or manage it via a prior apply — no view adoption in v1) bound view ref '{view}' is deleted in this document (state: absent) — revoke this token line (state: absent) first, or keep the view bound view ref '{view}' has no live view row (deleted out of band) — re-declare the view (state: present) or revoke this token (state: absent) read_filter {path|root}: predicate is missing a 'column' key read_filter {path|root}: link_target is missing a 'link' key read_filter {path|root}: node must be one of {"and": [nodes]}, {"or": [nodes]}, {"not": node}, a {"column", "op", "value"} predicate or a {"link", "quantifier", "target"} link_target leaf (got {type}) unresolved read_filter ref '{token}': {detail} unresolved visible_columns ref '{token}': {detail}

其中兩則在 apply 端另有寫法。受管理 view 的錯誤會加前綴,並把「for it」寫成「before this line」:

public_read '{table}.{ref}': view ref '{view}' is not a managed view in this document (declare a `view` line before this line, or manage it via a prior apply — no view adoption in v1) public_read v1 supports secretless tokens only; declare secretless: true (mint a Bearer-secreted token via REST)

把 grant filter 複製進 token spec 會在 mint 時失敗,因為公開 token 沒有操作主體:

read_filter may not reference $me / $me.department on a public token (no principal exists); $today and $now are allowed

command

unknown table ref '{tref}' command adoption requires the live command to match the declared spec; export the live command or use a distinct name ambiguous adoption: {n} live commands named '{name}' 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 scope

ambiguous adoption 是 diff 階段錯誤,與既有的 table 版本 ambiguous adoption: {n} live tables named '{name}' 同類——apply 回傳 200 且只略過該行。其餘四則會阻擋 applyability。

REST authoring 邊界拒絕的 definition 會回報為 invalid command definition: {reason}。{reason} 可能是原樣引用的 Pydantic CommandDefinition 錯誤文字,或下列之一:

callback '{name}' secret_ref does not address this callback callback '{name}' redacted url requires an existing live value callback '{name}' redacted header '{header}' requires an existing live value callback '{name}' redacted body requires an existing live value callback '{name}' redacted body value requires an existing live value callback '{name}' redacted body requires a matching live shape unknown column ref '{table}.{ref}' unknown column ref '{ref}' tagged column ref '{ref}' requires a table-qualified ref tagged column ref '{ref}' does not belong to source '{source}' command where predicate is malformed inline computed expression must be an object inline computed record source is unknown

callback … 那幾則來自用 live command 還原匯出時的 "<REDACTED>" callback 值;v1 definition 也可能回報不帶前綴的 unknown table ref '{ref}' 或 table '{ref}' no longer resolves live。definer ceiling(definition.table_access)中不是本文件或本 scope 之資料表 ref 的 key,只會在 apply 被拒絕,訊息是不帶 invalid command definition: 前綴的 unknown table ref '{ref}' in table_access;plan 不會解析 table_access。違反建立規則的版本 2 effect_identity 區塊,會把該規則的訊息當成 reason 回報,例如 invalid command definition: effect_identity key needs at least one row term on metadata id (an occurrence is anchored to a selected row)。

Record

table '{table}' has no resolvable key column for record matching key column '{key}' is a {boolean|json|interval} column and cannot be a natural key ({reason}) unknown column ref '{token}' in record.data decimal column '{internal_key}': {decimal error} table '{table}' accepts record writes only through Commands (write_policy: commands_only)

最後一則訊息帶 code: "governed_context_required",會為每一行落在「有效 write_policy 為 commands_only」之資料表的 record 行各回報一次——有效值是文件自己 table 行宣告的 settings.write_policy,沒有宣告時則取 live 資料表的。這是 validate 階段錯誤,所以 plan 不可 apply(422)、什麼都不會寫入;這類資料表的資料列只能透過 Command 變更。只有在文件自己的 table 行宣告了 write_policy: commands_only 時,工作台才會回報這則訊息(不帶 code);live 資料表的情況需要伺服器 plan,而需要 live 欄位的 decimal column … 訊息同樣只來自伺服器。

decimal cell 會在比對前依 live 欄位驗證,任何失敗都是 validate-phase 錯誤,plan 因此不可套用。{decimal error} 為下列之一:

decimal value must be a plain decimal string negative decimal values are not allowed negative zero is not a canonical decimal value decimal value has more fractional places than scale decimal value exceeds precision ct.numeric_overflow: decimal input is too long

JSON 數字一定會得到第一則錯誤:decimal cell 要寫成字串,例如 "12.50"。

Key 欄位的訊息涵蓋三種不可作為 key 的型別。{reason} 在 boolean 時是 a two-value domain cannot uniquely match records,在 json 與 interval 時是 {type} values have no canonical bind representation and cannot uniquely match records。Principal(principal/user/social_client)欄位可以當自然鍵。

精確來源:components/iac/parse.ts。伺服器 plan 看見 live records 後還可能回傳:

record data missing required key column value ('{internal_key}') key column not resolvable: {detail} duplicate key value {value_repr}: {count} record lines in this document declare the same new key ambiguous key value {value_repr}: {count} live records match

這四則屬於 diff 階段:apply 回傳 200 並略過該行。上方第一個區塊的每一則訊息——key 欄位、未知 ref、decimal column … 與 governed_context_required——都是 validate 階段,會擋下整份文件(422)。

Diff 階段的 Claim 與搬移錯誤

以下屬於 diff 階段:它們附在 plan action 與 errors[] 上,apply 回傳 200 且只略過那一行。

{kind} is managed by system '{system}' (ref '{ref}') claimed_by={'system': '{system}', 'ref': '{ref}'} ref '{ref}' and existing ref '{other ref}' bind the same {kind} — if you renamed the ref, use renamed_from renamed_from '{old qualified ref}' has no state row and '{qualified ref}' has no state row either cannot move: both 'renamed_from' ('{old qualified ref}') and '{qualified ref}' already have state rows — if this is not a rename, drop renamed_from ambiguous view adoption: {n} shared views named '{name}'

renamed_from 的舊 ref 沒有 state row、而新 ref 已經有時,不是錯誤:搬移已經完成,該行會以新 ref 規劃。Apply 時若 claim 與其他寫入者競爭而失敗,該行會以 {kind} '{qualified ref}' resource is already claimed by another system/ref 失敗。

Plan 警告

警告永遠不會阻擋 apply。仍請檢查,因為其中幾則代表後續步驟會失敗:

ref not managed; adopt before deleting ref not managed; nothing to remove ref not managed; nothing to revoke grant not present; nothing to revoke insight selection not present; nothing to remove absent table '{ref}' is still targeted by link columns on: {tables} — export/reimport of the remainder will fail until those are removed table relies on the positional-default key column for record matching; set key explicitly key changed ({explicit|implicit (positional re-default)}) from '{old}' to '{new}' — seed matching and link-by-key cells re-interpret under the new key; previously seeded rows may re-insert grant target table is itself being created in this apply; grant legality cannot be fully verified until then scope_values omitted on a channel-governed table; silence preserves the current value, explicit null denies client_access target table is itself being created in this apply public_read target table is being created in this apply; column-ref and mint-policy legality is verified at apply time insight system tag does not exist yet; it is created on apply — selection legality cannot be fully verified until then declared cells drifted; on_drift=skip leaves the live row unchanged record classification deferred to apply (references a same-doc column not yet created) column already removed from the schema; apply finishes its cell cleanup {table ref no longer resolves|table ref resolves to a replacement}; original table '{table id}', column '{internal key}' retains its cells and state. An operator must clean up or purge the original table before removing this absent declaration.

前五則回應資源未受管理的 state: "absent" 行:table、column 與 command 行得到 adopt before deleting(table 行可以帶 spec,一步完成 adopt 與刪除),rule、trigger、view 與 insight_selection 行得到 nothing to remove,grant 與 public_read 行得到 nothing to revoke。每一個省略 scope_values 的 present 聊天室主體 grant 都會出現 scope_values 那則警告。

最後兩則屬於 state row 比欄位或表格活得更久的 absent column 行。第一則規劃 delete:先前的刪除已把欄位從 schema 移除,卻在清理 cell 之前被中斷,所以在 company 或 department 文件中,apply 會完成清理並移除 state row。第二則在該行規劃 orphaned:原表格已不存在或已被取代,apply 對該行不寫入任何東西。

Apply 階段

以下錯誤出現在 200 apply 回應的 per-line 錯誤中。失敗的行會回滾自己的 transaction,apply 再繼續處理其他可執行的行。

moderators not found, deleted, or outside this table's company service_account_managed_by_department cannot resolve this table's company for moderator validation passphrase: {message} settings.default_permissions.audience "company" is only valid on department-scoped tables settings.default_permissions.audience must be "scope" or "company", got '{value}' command '{ref}': table no longer resolves live command '{ref}' has pending staged executions command '{ref}': tag not found in this scope command '{ref}': tables outside tag: {table id}, {table id} command '{ref}': document system tag is required a command named '{name}' already exists in this scope command '{ref}' is referenced by table triggers (command_referenced_by_trigger): table '{name}' ({id}) trigger '{name}' ({id}) command changed after planning; run plan again trigger changed after planning; run plan again Concurrent write conflict (lock); please retry the request. insight_selection '{insight.uuid}': chatroom does not exist or is deleted insight_selection '{insight.uuid}': chatroom belongs to a different company insight_selection: chatroom token '{token}' now resolves to a different chatroom than it did at plan time; run plan again principal column '{internal key}': {token error} view '{table}.{ref}': invalid configuration ({n} field error(s)) Date period is invalid. Date period overlaps another period in this partition. Date period rules changed during this write; retry. this write needs approval, which a deferred record group cannot stage: apply again once its link targets exist, or split a link cycle (rows first, then links) Table rules changed during write; retry

每一種 kind 都有自己的 apply 端字串,大多表示 plan 時解析到的東西已經不在:

table '{ref}' no longer resolves live table '{ref}' not found for deletion table was deleted while the apply was in flight Table '{name}' already exists in {this chatroom|this department|another department in this company|this company} column '{table}.{ref}' not found for deletion column '{table}.{ref}' retains cells in original table '{table id}'; re-plan to review its orphaned action {kind} '{qualified ref}' resource is already claimed by another system/ref {rule|trigger} '{table}.{ref}': table no longer resolves live {rule|trigger} '{table}.{ref}': adopt with no resolvable native id {rule|trigger} '{table}.{ref}': system tag sync failed: {exception} {rule|trigger} '{table}.{ref}': {reason} table is currently locked for operation: {operation} require_approval rule edit blocked: {n} staged change(s) pending/applying — resolve or discard them first view '{table}.{ref}': adopt with no resolvable state row view '{table}.{ref}': table no longer resolves live view '{table}.{ref}' no longer resolves live view '{table}.{ref}': {reason} grant '{table}.grant:{principal type}': {token error} principal token '{token}' now resolves to a different principal than it did at plan time; run plan again grant '{table}.grant:{type}:{id}[:{audience}]': table no longer resolves live insight_selection: {token error} client_access '{table}.client_access': table no longer resolves live passphrase_enabled with '<REDACTED>' requires an existing live passphrase hash public_read '{table}.{ref}': table no longer resolves live unresolved ref '{token}': {detail} record '{table}' line {n}: table no longer resolves live record '{table}' line {n}: governed_context_required — table accepts record writes only through Commands (write_policy: commands_only) unknown column ref '{token}' in record.data link column '{internal key}': target table not resolvable command '{ref}': adoption target no longer resolves command '{ref}' references no table: a command must operate on at least one custom table in this scope command '{ref}': approval_trigger_conflict — this command is invoked by a table trigger and cannot add a reference to table '{name}' ({table id}), which requires approval (rule {rule id}) command '{ref}': effect_identity_locked — {message} command '{ref}': authority `definition` requires execute_policy `restricted` command '{ref}': execute grant principal {type}:{id} not found command '{ref}': invalid execute grant unknown table ref '{ref}' in table_access command '{ref}': system tag sync failed: {exception} command '{ref}': {ref error}

Trigger 行的 apply 錯誤會加上 trigger '{table}.{ref}': 前綴,多個原因以 ; 串接:它的身分 token、invoke_command 的 ref 與輸入、materialize_slots 的表格 ref、無法保留的 secret(此時 {reason} 是上方的 trigger secret 訊息之一),或 trigger 驗證器自己的訊息。Rule 行的原因是 rules 驗證器的訊息。Grant 或 passphrase_permissions 的驗證失敗只回報第一則訊息,不帶前綴。public_read 行回報 mint policy 的第一個失敗,同樣不帶前綴:Cannot publish a table governed by a scoped channel policy、View not found、view config 的訊息、View uses cross-table computed filters/sort and cannot be published for public read、visible_columns may not publish column '{name}' (attachment/link/rollup/lookup columns are never public)、某一則 row policy … 文法字串,或下方的 $me 訊息。

{kind} … adopt with no resolvable native id 與 view … adopt with no resolvable state row,是目前 rule、trigger 或 view 的每一個 adopt action 的結局:apply 會尋找 adopt 還沒有的 state row,因此該行失敗、不寫入 state,下一次 plan 又會規劃 adopt。Table、column 與 command 可以正常 adopt。要讓這類 rule、trigger 或 view 受 IaC 管理,請先透過 REST 移除 live 的那一份,讓該行規劃 create。

Record 行中基數為 many 的 link cell 會在所有 record 行之後才解析,失敗時以 ref: "record:{line}" 回報,所有原因以 ; 串接:target table '{table}' has no resolvable id、unknown target table ref '{table}'、target table '{table}' has no resolvable key column、target table '{table}' key column '{key}' is not resolvable、target table '{table}' key column not resolvable: {detail}、no row in '{table}' with key value {value}、ambiguous key value {value} in '{table}': {n} live records match,或 token 錯誤。該 record 自己的結果仍是 applied: true:它的純量與基數為 one 的 link cell 已經寫入。基數為 one 的 link 若出現相同的原因,則改為整行被拒絕(ref: "record:{line}"、applied: false,沒有寫入任何東西);見原子行與延後群組。

principal column '{internal key}': {token error} 是上方身分 token 錯誤在 apply 端的對應形式。只有當該 record 行的 principal 欄位是同一份文件較早處宣告時才會走到它——plan 會帶著 warning 延後該行,executor 再重跑一次替換,因此失敗是以 200 內的 applied: false 出現,而不是 422。見 record line kind。

Link cell 的解析失敗——包含 principal 自然鍵目標的 link 清單裡的身分 token——回報時帶 ref: "record:{line}",不帶純量 record 錯誤所使用的 {table}. 前綴;兩個例外是無法解析的目標表(link column '{internal key}': target table not resolvable),以及 cell 在寫入時被拒絕(驗證錯誤或 Date period … 代碼),它們帶 ref: "{table}.record:{line}"。延後 record 群組中每一行的拒絕,不論原因(包括 Date period … 代碼與 approval_staging_unavailable),都帶 ref: "record:{line}"。

三則 moderator 字串會帶 code: "invalid_moderator_target";它們取代了舊的 moderators not found (or deleted): [...] 與 moderators outside this table's company: [...],而且不再列出有問題的 id。command changed after planning; run plan again 與 trigger changed after planning; run plan again 會帶 code: "iac_plan_stale"。若 apply exception 被分類為 MySQL 1205 或 1213,則會帶 code: "retryable_lock_conflict",detail 是淨化後的 Concurrent write conflict (lock); please retry the request.;回應絕不洩漏原始資料庫文字、SQL 或參數。command '{ref}': effect_identity_locked — {message} 會帶 code: "effect_identity_locked":該 command 已記錄過業務事件,所以它的 effect identity 不能變更或移除,而且它不是重試訊號。三則 Date period … 字串依序帶 code: "date_interval_exclusion_invalid_period"、"date_interval_exclusion_overlap" 與 "date_interval_exclusion_retry",而且絕不指出衝突的資料列、邊界或 partition 值;它們來自 record 行、在所有行之後寫入的 link 儲存格,或被既有資料列違反的 rule 行,其中 …_retry 是重新 plan 的訊號,Table rules changed during write; retry 也是。this write needs approval… 這則帶 code: "approval_staging_unavailable":指向後面某行才會建立之資料列的 record 行,會在無法暫存審批的延後群組中套用,所以符合它的 require_approval rule 會拒絕它、且不暫存任何東西(見 record 行類型)。record '{table}' line {n}: governed_context_required — … 是同 code 之 plan 錯誤在 apply 時的對應;plan 錯誤才帶這個 code。其他 diagnostics 的 code 不設值。

command '{ref}': table no longer resolves live 刻意統一措辭:它不區分「不存在」與「不屬於你」。view 那一行在 spec.config 帶了 CustomTableViewConfig 沒宣告的 key 時觸發——config 在 parse 階段是自由 dict,工作台攔不到,這個檢查在 apply 時 fail-closed 執行(見 view line kind)。

有兩則 apply 錯誤帶 line: 0,因為它們無法歸屬到任何文件行——兩者都以 kind: "table" 與 header 的 system 作為 ref(apply 的最後一步——重寫仍帶有憑證的舊 trigger state 列——失敗時,還可能接著出現第三則,trigger state rewrite failed ({ExcType}),kind 為 "trigger",detail 不含任何值):

tag sync failed: {exception} orphan prune failed: {exception}

Orphan-prune maintenance branch 也使用同一個 lock classifier。若其 exception 是 MySQL 1205 或 1213,line-0 detail 會是 orphan prune failed: Concurrent write conflict (lock); please retry the request.,且 code 為 retryable_lock_conflict。這同樣要求重新 plan;不可因此重播舊 apply body。

Apply 會淨化那些文字可能夾帶明文密語、trigger 憑證或未經淨化 integrity error 的例外類別:

Database operation failed. invalid configuration ({n} field error(s)) ← view 行會帶前綴:view '{table}.{ref}': … client_access configuration is invalid ({ExcType}) command configuration is invalid ({ExcType})

Database operation failed. 會取代任何不是 lock 衝突的資料庫 driver 錯誤。

看到這幾則時,真正的原因在伺服器日誌,不在回應裡。table、column、rule、trigger、view、grant、public_read 或 record 行上的其他例外會原樣以自己的文字帶出——例如翻譯時無法解析的 ref 會以 line {n}: unresolved ref '{token}': {detail} 出現,CRUD 的拒絕則是那則拒絕本身的訊息。

文件層級的狀態碼

狀態碼Detail時機
200IacApplyResponse同步 apply 已完成。仍須檢查 per-line results 與 errors;HTTP success 不代表每一行都成功
202帶 ticket_id 的 IacApplyTicketResponseParse、validation、plan 與 hash 檢查通過後,record-line 數量超過同步上限、文件包含重量級欄位變更,或(自後端 5.22.0 起)它的 plan 建立、更新、刪除或搬移的資源超過變更上限;或重複的 idempotency_key replay 了原本的 ticket。請以送出 apply 的使用者身分輪詢 task route,並且 200 與 202 都要處理
400Flat detail 字串JSONL 格式錯誤、UTF-8 無效、某一行不符合其 kind 的 schema 或 model validator,或撞到 document/line/table/record cap。完全沒有執行,也不會建立 ticket
400{"code": "ct.iac_legacy_backfill_unavailable", "message": "LEGACY backfill is available only when LEGACY is the default worker."}部署的預設 worker 不是 legacy 時,對重量級欄位變更指定 worker=legacy(若該 plan 同時也是變更量大的 plan,會得到下一列的訊息)。沒有暫存或 dispatch 任何東西
400{"code": "ct.iac_legacy_backfill_unavailable", "message": "LEGACY apply of a plan with more changes than the synchronous limit is available only when LEGACY is the default worker."}自後端 5.22.0 起:部署的預設 worker 不是 legacy 時,對變更量大的 plan指定 worker=legacy;文件同時有超過 200 條 record line 時亦然。沒有暫存或 dispatch 任何東西
403只有一筆的 detail list:code: "iac_permission_denied"、detail: "IaC resource authorization denied",以及被拒絕那一行的 line 與 kind。若被拒絕的是呼叫者本身(呼叫者已無法解析為該公司的 live 使用者),則 line 為 0、kind 為 null;plan 為整份文件檢查的表格(例如文件所依賴的另一個 system 所擁有的表格),則回報為 line: 1、kind: "header",不論第 1 行實際是什麼Plan 或 apply 引用了呼叫者不得使用的資源,例如沒有所需權限的表格,或是不存在、屬於其他公司、無法讀取的表格 id。回傳的是最先發現的拒絕:先依文件順序檢查每一行引用的資源,再檢查整份文件依賴的表格(回報為第 1 行),最後檢查每個 record 行的寫入權限,所以不一定是最早被拒絕的那一行
404Route 層為 Chatroom Not found 或 Department not found;apply 期間為 Chatroom not found、Department not found 或 Company not foundRoute 無法解析指定的聊天室(不存在或已刪除)或部門(不在你的公司);company route 沒有 scope 404。Apply 期間,聊天室文件在每次變更前都會重新鎖定聊天室,聊天室在 apply 途中被刪除時回 Chatroom not found;command 行會取得自己的三種 scope command-root lock,該 scope 已不在時回 Department not found 或 Company not found。其他 line kind 沒有一般的 department/company mid-apply fence
409{"code": "plan_stale", "message": "…", "plan_hash": "…"}Apply 帶的 plan_hash 與重算的 plan 不符。完全沒有執行
409{"code": "ct.iac_idempotency_key_reused", "message": "This idempotency_key was already used with a different IaC document."}idempotency_key 已綁定到另一份文件(以完整位元組比對),而這次 apply 會被 dispatch 成 ticket。沒有 dispatch 任何東西
4xx / 500該次寫入自己的 HTTP 錯誤基數為 many 的 link cell 因驗證與日期期間 rule 以外的原因被拒絕——例如 write-policy 或 SCP 的 403、審批暫停或 lock 的 409,或 500(見 record 行類型)。先前各行已 commit
422重算 plan 的 validate-phase 錯誤清單Apply 的文件帶有任何 validate 階段錯誤。完全沒有執行
422只有一筆的 list,其 code 為 ct.iac_time_budget_exceededPlan、state、export,或 apply 的唯讀階段超過伺服器時間預算;見 Request 時間預算。沒有寫入任何東西
500{"code": "ct.iac_dispatch_failed", "message": "The background apply could not be submitted. Retry later."}Ticketed apply 無法提交給 batch、sqs 或 pubsub worker。Ticket 會記錄同一個錯誤,idempotency_key 也繼續綁在該 ticket 上
500Internal Server Error有未預期的例外從整個 plan 或 apply 拋出。進行中的交易已回滾(apply 已 commit 的行仍保持已套用),伺服器日誌只記錄例外型別與資料庫錯誤代碼、絕不記錄例外文字,因為文件可能帶有唯寫的 trigger secret

Validator 訊息類別

這些 detail 來自會在 line model 上拋出一般 ValueError 的 Pydantic validator。它們和其他 parse 失敗的回應方式相同,plan 與 apply 皆然:flat 400,detail 是 line {n}: 加上 issue list,每個 issue 都是 value_error,msg 以 Value error, 開頭,loc 指出 line kind 與欄位。issue 不帶 ctx,所以過去讓序列化失敗、並在後端 #1774 之前回 500 {"detail": "Internal Server Error"} 的例外物件不會再出現。工作台產生相同的 detail,但下方清單最後兩類只有伺服器會檢查。例如:

line 3: [{"type": "value_error", "loc": ["rule"], "msg": "Value error, state=absent lines carry no spec (table lines are the sole adopt-then-delete exception)", "url": "https://errors.pydantic.dev/<pydantic-version>/v/value_error"}]
moderators must be user UUIDs or $user:<username> tokens; rejected [{values}] settings.{owned_key} is server-owned and cannot be set via IaC 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) state=present lines require a spec principal.audience is required when principal.type=='chatroom' principal.audience is only valid when principal.type=='chatroom' grant spec 'can_insert' must be a boolean (true/false) or null, not {python_type} grant spec '{field}' must be one of ['all', 'filtered', 'none', 'own'] or null, got {value_repr} rpm may not be null — omit the field to keep the mint default (60) allow_query may not be null — omit the field to keep the default (false) settings.write_policy must be one of ['any', 'commands_only'] definition_requires_restricted: authority `definition` requires execute_policy `restricted` authority `definition` requires a non-empty table_access authority `definition` is not supported for query-mode commands table_access is only allowed with authority `definition` table_access.read `filtered` requires read_filter table_access.read_filter is only allowed with read `filtered` callback url must be an absolute http(s) URL callback url must not contain credentials; use callback headers callback url host cannot be templated callback secret_ref must address its callback name {label}: expression node count exceeds {max}

最後兩則來自 command definition,而且只有伺服器會產生:secret_ref 檢查(只適用於 callbacks 的各個 entry),以及在 definition 解析前檢查的運算式預算(definition expression node count exceeds 8192,以及逐列、where 與 $case 的限制(以 step 與 row 標示)、以 rule '{name}': … 標示的 rule 預算,和未標示的 command definition: JSON depth exceeds 64、command definition: JSON node count exceeds 16384 與 command definition exceeds 1048576 bytes)。結構本身不合法的 definition 也以相同方式回 400。

owned_key 的實例是 column_mapping 與 iac;grant spec 那一類的 field 是 can_read 或 can_edit。伺服器永遠走不到 moderators 那一則:每個項目自己的 pattern 會先以一般的 string_pattern_mismatch parse 400 拒絕錯誤項目。present-requires-spec 這道 guard 有兩種寫法:command 與 public_read 沒有 renamed_from,因此它們的訊息不帶純改名的括號補述。rpm … 與 allow_query … 兩則是 public_read 的 spec validator,在那裡「省略」與「填 null」是真的不同。這四種原本未型別化的輸入,現在會以 typed、不含提交值的錯誤拒絕,不再是舊有的 500;見下方格式錯誤輸入表。工作台仍是近似實作,不是伺服器驗證器。

state=… 與 grant spec … 這兩類只有在該行每個欄位都合法之後才會執行,所以同一行上缺少欄位、型別錯誤或未知 key 會先被回報,並把它們蓋掉,而且只會回報其中第一個失敗的。principal.audience …、table_access.read …、authority …、table_access is only … 與 definition_requires_restricted 這幾類驗證的是巢狀物件,所以只有該物件內的錯誤會蓋掉它們;settings.…、rpm …、allow_query … 與 callback url … 這幾類驗證的是它們自己的欄位,會和不相關的欄位錯誤並列,依欄位順序排列。各類訊息的 loc:

訊息類別loc
state=…["{kind}"]——column、rule、trigger、view、grant、command 或 public_read
settings.…["table", "spec", "settings"]
principal.audience …["grant", "principal"]
grant spec …["grant"]
rpm …、allow_query …["public_read", "spec", "rpm"]、["public_read", "spec", "allow_query"]
definition_requires_restricted: …["command", "spec"]
authority …、table_access is only …["command", "spec", "definition"]
table_access.read …["command", "spec", "definition", "table_access", "{table ref}"]
callback url …["command", "spec", "definition", "callbacks", {index}, "url"]

未知 key 在不同時機被攔下,而有五個地方完全不攔。頂層拼錯的 key,或具型別 spec(IacTableSpec、IacCommandSpec、IacPublicReadSpec、IacClientAccessSpec、IacGrantPrincipal、IacTriggerSecretSlot)內的未知 key,在 parse 階段被拒絕;trigger 行的 spec 或其 actions 之一內的未知 key 也一樣:extra_forbidden,loc 為 ["trigger", "spec", "{key}"] 或 ["trigger", "spec", "actions", {index}, "{key}"],key 依名稱排序、最多保留三個 issue、絕不回顯送出的值。trigger 接受 id、name、on、when、transition、schedule 與 actions;action 接受 type、id 以及它自己 type 的 key(見 trigger 行類型),type 不是已知類型的 action 則不接受其他任何 key。column、rule、view 與 grant 的 spec 是自由 dict:column 的未知 key 在該行建立 column 時會於 apply 經由 ColumnCreate 讓該行失敗——對既有 column,該 key 會被丟掉,而且每次 plan 都會拿它比較,該行會永遠規劃 update——view 的 spec.config key 則由 CustomTableViewConfig 攔下(view '{table}.{ref}': invalid configuration …)。Rule 的未知 key,date_interval_exclusion 與 same_dimension_reference 會在 plan 時被拒絕(invalid … rule fields: {key}),unique_tuple(Extra inputs are not permitted)、channel 與 invariant({type} rule has unknown key(s): …)則在 apply 時被拒絕;驗證器會依它認得的欄位重建其他每一種 rule,所以該 key 會被丟掉,而且每次 plan 都會拿它和 live rule 比較,該行會永遠規劃 update。View spec 頂層的未知 key 也一樣:既不會被拒絕也不會被套用;grant spec 的未知 key,以及 client_access 的 passphrase_permissions 內的未知 key,則會被靜默忽略。

Typed 格式錯誤輸入拒絕

這四項是特定格式錯誤輸入的 typed 修正,不是所有伺服器錯誤都變成 400 的保證:

type輸入靜態 msg
grant_access_level_typeIaC grant 的 can_read 或 can_edit 為 list/objectGrant access level must be a scalar or null
command_step_kind_typeList/object 的 v2 step kindCommand version 2 step kind must be a scalar
public_read_valid_until_overflow轉成 UTC 後超出 1–9999 年的 expiryPublic-read expiry is outside the UTC datetime range
json_integer_too_long超過安全 parse 上限的 IaC 整數字面值JSON integer exceeds the safe digit limit

REST 的 Command definition 與 public-read expiry 錯誤使用 400 {detail: [{type, loc, msg}]};靜態 issue 不含 input 或 ctx,其他驗證仍是 422。grant_access_level_type 來自 IaC grant model,不是一般 REST permission payload。IaC plan/apply 在寫入前中止 parse,以 400、detail 字串 line N: <JSON issue list> 及不含提交值的 issue 回報。超長整數的 list 是:

[{ "type": "json_integer_too_long", "loc": [], "msg": "JSON integer exceeds the safe digit limit" }]

JSON 整數上限最多 4300 位數,不計開頭負號;Python 啟用更嚴格上限時取較小者。引號內的十進位文字不是整數字面值。這些邊界不能只依工作台本機成功判定;請執行 server plan。

行事曆與序列代碼

IaC line error 可帶 ct.scope_closed、ct.scope_dependency_in_use,以及 ct.sequence_server_assigned、ct.sequence_date_required、ct.sequence_scope_required、ct.sequence_scope_pinned、ct.sequence_exhausted、ct.sequence_config_immutable、ct.sequence_dependency_in_use、ct.sequence_series_in_use、ct.sequence_series_incompatible、ct.sequence_key_unavailable。各項拒絕見編號序列與期間圍欄。Plan 不會保留編號,也不會讓 apply 免除目前期間檢查。

本機與伺服器驗證

IaC 工作台是無 I/O 的早期回饋:它認得全部十二種 line kind,包含 command、insight_selection 與 public_read,並驗證 Pydantic 欄位、ref graph、grant policy、public-read token policy 與 record key。它不能讀取 IaC state 或 live scope,因此下列判斷一定要 server plan:

  • ref 是否已 claim 一個仍存在的 resource,或能否唯一 adopt;
  • display/internal column mapping 與跨表 live IDs 是否可解析;
  • live drift、依賴、授權範圍與 resource-specific server constraints;free-dict CRUD model 的最後一層檢查可能直到 apply phase 才執行;
  • record natural key 是否重複或在 live table 中不唯一;
  • 文件大小、table/line/record 部署上限。

有幾類規則在結構上就超出本機能力,一定要伺服器 plan:

  • 與 scope 相關的檢查。 解析器沒有 scope 脈絡,因此 insight_selection is only valid for department-scope documents 與 settings.default_permissions.audience "company" is only valid on department-scoped tables 永遠不會在本機觸發。它能證明 header.system 那一道,而且確實有做。
  • 身分 token 解析。 本機只檢查到 pattern;$dept:Front Desk 是否存在、是否已刪除、是否有歧義,都是以公司為錨點的伺服器工作。這也表示 duplicate insight_selection line 中「解析後才碰撞」的形式只有伺服器抓得到——本機只有字面完全相同才會碰撞。
  • 受管理 view 的綁定。 public_read 的 view 可能由先前 apply 管理,解析器看不到,因此它不嘗試「這個 view 是否受管理」的檢查。它會執行純文件內的 CASCADE 防護:當被綁定的 view 在同一份文件中宣告為 absent 時。
  • Command definition。 只有 definition.steps[].table 會對文件內的資料表 ref 檢查。參照解析屬於伺服器 compiler,definition 自己的 parse 階段 validator 也是:運算式與 JSON 預算、secret_ref 規則、version-2 callback 必須明確帶 body、callback 其餘的形狀(缺少 name、method 不是 POST、PUT 或 PATCH、headers、body 大小、callback needs exactly one of url or api_tool_id)、callback 名稱不得重複與 trigger references unknown callback(s): …、mode: "query" 的 definition 不得有寫入 step,以及 step、input、rule、trigger、output、example 與 effect_identity 的形狀(包含其中的未知 key)。伺服器對這些回 parse 400;工作台則會接受該文件。工作台確實會檢查每個 callback 的 url:型別、長度 1–2048、urlsplit 的方括號 host 與 NFKC netloc 檢查,以及 callback url … 規則。
  • 其他 parse 與 plan 階段的 spec。 public_read.spec.valid_until 的日期解析,以及 same_dimension_reference 的 spec 檢查(invalid reference rule fields: …),同樣只有伺服器會做。
  • Public-read mint policy。 SCP channel 規則、強制隱藏欄位的允許清單、跨表 computed 語法與 row-policy token 的合法性,都在每次 apply 重新執行,本機從不重現。

工作台是近似 parser,而不是重新實作。仍有差異:issue 順序;public_read.spec.secretless 可接受但本機未正規化的 false 寫法;未執行的 settings.default_permissions.audience 檢查;passphrase 以外的孤立 surrogate;Python splitlines()/strip() 與本機切行/trim();__proto__ key;Python 風格診斷引號中的非 ASCII 不可列印字元;以及數字寫法/轉型邊界,如 10.0 失去 float 寫法、"60.0" 未通過本機整數字串檢查。本機已處理 boolean 轉整數。序列欄位與 period_fence rule 經由通用 object spec 與共用 line/ref/state 檢查,不含功能專屬本機驗證。請以伺服器為準。本機成功代表可進入 server plan,不是 apply;plan 成功後仍須逐行檢查 apply 的 applied 值/錯誤。

修正順序

  1. 先修正 JSON 與唯一 header,讓所有 lines 能 parse。
  2. 再修正每個 kind 的 required/optional fields 與型別。
  3. 依文件順序修正 table refs、qualified-ref 碰撞與欄位 refs。
  4. 修正 filtered grant、row policy 與 record natural key。
  5. 重新執行本機驗證,再送 server plan;對 live-only error 以 plan evidence 修正。
  6. 只有在 plan 完全可解釋並通過審查後才 apply。

動手試試

把一份 JSONL 貼進 IaC 工作台,依序製造一個 JSON parse error、一個跨 kind ref collision 與一個缺少 read_filter 的 filtered grant。確認 phase/line/detail 後逐一修正,再對測試 scope 執行唯讀 plan。

Last updated on