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_tuplerule 拒絕的寫入: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(呼叫者不得使用的資源;403body 中唯一那一筆的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} bytes | CUSTOM_TABLE_IAC_MAX_BYTES | 5 MiB(5,242,880 bytes) | 原始 request body |
line cap exceeded ({max_lines}, record lines exempt) | CUSTOM_TABLE_IAC_MAX_LINES | 2,000 | 非 record 行;header 與空白行不計入 |
table cap exceeded ({max_tables}) | CUSTOM_TABLE_IAC_MAX_TABLES | 40 | table 行 |
record cap exceeded for table '{table}' ({max_records}) | CUSTOM_TABLE_IAC_MAX_RECORDS | 200 | 單一表格的 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 translationfallback.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 deletedservice_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 optionsdecimal 欄位的 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_ 開頭。
SCP link leaf 不會被拒絕
在 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 allowedcommand
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 scopeambiguous 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 unknowncallback … 那幾則來自用 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 longJSON 數字一定會得到第一則錯誤: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 | 時機 |
|---|---|---|
200 | IacApplyResponse | 同步 apply 已完成。仍須檢查 per-line results 與 errors;HTTP success 不代表每一行都成功 |
202 | 帶 ticket_id 的 IacApplyTicketResponse | Parse、validation、plan 與 hash 檢查通過後,record-line 數量超過同步上限、文件包含重量級欄位變更,或(自後端 5.22.0 起)它的 plan 建立、更新、刪除或搬移的資源超過變更上限;或重複的 idempotency_key replay 了原本的 ticket。請以送出 apply 的使用者身分輪詢 task route,並且 200 與 202 都要處理 |
400 | Flat 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 行的寫入權限,所以不一定是最早被拒絕的那一行 |
404 | Route 層為 Chatroom Not found 或 Department not found;apply 期間為 Chatroom not found、Department not found 或 Company not found | Route 無法解析指定的聊天室(不存在或已刪除)或部門(不在你的公司);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_exceeded | Plan、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 上 |
500 | Internal 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_type | IaC grant 的 can_read 或 can_edit 為 list/object | Grant access level must be a scalar or null |
command_step_kind_type | List/object 的 v2 step kind | Command version 2 step kind must be a scalar |
public_read_valid_until_overflow | 轉成 UTC 後超出 1–9999 年的 expiry | Public-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)。伺服器對這些回 parse400;工作台則會接受該文件。工作台確實會檢查每個 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 值/錯誤。
修正順序
- 先修正 JSON 與唯一 header,讓所有 lines 能 parse。
- 再修正每個 kind 的 required/optional fields 與型別。
- 依文件順序修正 table refs、qualified-ref 碰撞與欄位 refs。
- 修正 filtered grant、row policy 與 record natural key。
- 重新執行本機驗證,再送 server plan;對 live-only error 以 plan evidence 修正。
- 只有在 plan 完全可解釋並通過審查後才 apply。
動手試試
把一份 JSONL 貼進 IaC 工作台,依序製造一個 JSON parse error、一個跨 kind ref collision 與一個缺少 read_filter 的 filtered grant。確認 phase/line/detail 後逐一修正,再對測試 scope 執行唯讀 plan。