grant line
grant 沒有 authored ref;它以 table + principal natural identity 管理一列 ACL。這讓 user、department、client 與 chatroom audience 的授權都能 plan、審查、套用與明確撤銷。
JSONL Line
{"kind":"grant","table":"orders","principal":{"type":"chatroom","id":"33333333-3333-4333-8333-333333333333","audience":"internal"},"spec":{"can_read":"filtered","can_insert":false,"can_edit":"none","visible_columns":["order_no","status"],"read_filter":{"and":[{"column":"status","op":"eq","value":"open"}]},"scope_values":["support"]}}欄位契約
頂層對應 IacGrantLine:
| 欄位 | 必填 | 精確契約 |
|---|---|---|
kind | 是 | 固定 "grant" |
table | 是 | target table ref |
principal | 是 | typed IacGrantPrincipal object |
state | 否 | present/absent,預設 present |
spec | 否 | object 或 null;absent 時禁止提供 |
principal 只接受 type、id、audience。type 必須是 user、department、client、chatroom;id 必填、1–160 字元並符合 ^([a-z0-9][a-z0-9_.-]{0,35}|\$(user|dept|smc|room):[^\r\n]{1,150})$——可以是最多 36 字元、不含冒號與空白的原始 ID,或是前綴與 type 相符的可攜身分 token(user → $user:、department → $dept:、client → $smc:、chatroom → $room:);audience 只有 chatroom 必填且只能是 internal/external,其餘 type 禁止。
Token 會在衍生的 {table}.grant:{type}:{id} ref 建立之前,以公司為錨點 fail-closed 地解析,因此指向同一 principal 的 token 文件與原始 ID 文件會收斂到同一列 state,而不是新增一筆重複 grant。
IaC Pydantic model 把 spec 定義為 Dict[str, Any],並在 parse 先守住三個危險值型別;server grant validators 再處理完整語意。引擎支援的精確欄位是 can_read、can_insert、can_edit、visible_columns、read_filter、edit_filter、scope_values。can_read/can_edit 是 none|own|all|filtered|null;can_insert 是 boolean/null;三者明確 null 分別正規化為 none/false/none。其他欄位可為 refs/policy/array 或 null。Spec 省略合法:新 grant 使用安全 write defaults,既有 grant 的未宣告 fields 保留 live 值。
Ref 與身分規則
Derived state ref 是 {table}.grant:{type}:{id};chatroom 再加 :{audience},例如 orders.grant:chatroom:33333333-3333-4333-8333-333333333333:internal。同一 principal 的 internal/external grants 是兩個不同 identities。
visible_columns entries 與 read_filter/edit_filter predicate column 都是 target table 的 column refs,不是 display names 或 internal keys。未知 token fail closed。link_target leaf 把這條規則拆到兩張表上:它的 link 是本行 table 的 ref,target 內每個 column 則是被連結那張表的 ref。相對地,predicate 的值可以帶 ACL row-policy token $me、$me.department、$today、$today±Nd(N 為 1 到 730,Asia/Taipei)與 $now——這套語法與上面的身分 token 及 command DSL 都互不相干。Grant derived refs 不占用 column/rule/trigger/view/public_read 的 authored child-ref slug,但同一 natural identity 不應在文件重複宣告。
若 principal.id 中的 token 無法解析,plan action 會帶一個 {table}.grant:{type}:unresolved-{8 個十六進位字元} 形式的佔位 ref,好讓錯誤仍可歸屬。它不是資源身分。
生命週期與規劃
Present grant 會對 natural identity create、update 或 noop,並以 last-applied fields 偵測 drift。只有 declared fields 由 IaC 管理;省略保留 live value。Filtered access 必須有對應的非空 policy——任一棵由 predicate 與 link_target leaf 組成的 and / or / not 樹——且非 filtered mode 不得保留 filter。
state: "absent" 不帶 spec,規劃 delete/revoke;若 live grant 已不存在則 noop warning。單純省略 line 不代表撤銷。Chatroom grant 另由 server plan 驗證 table department scope、company/chatroom 關係與 scope_values。
驗證錯誤
Audience 與 grant value 的 byte-exact local-only detail:
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 str
grant spec 'can_read' must be one of ['all', 'filtered', 'none', 'own'] or null, got 'read_only'Ref/policy validate detail:
unknown table ref 'orders'
unresolved visible_columns ref 'missing': unknown column ref 'orders.missing'
unresolved read_filter ref 'missing': unknown column ref 'orders.missing'
unresolved edit_filter ref 'missing': unknown column ref 'orders.missing'
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 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 or[1] "and" must be a non-empty list of nodes
row policy "not" takes exactly one node, not a list
row policy and[0]: leaf node has unknown key(s): extra
row policy: link_membership leaves are not supported in row policies — use link_target with a target {"column": "id", "op": "in", "value": [...]}
row policy: quantifier must be "any" or "all"
row policy: require_present must be stated explicitly (true/false) on quantifier "all" — all([]) is vacuously true
row policy: require_present does not apply to quantifier "any" (already false when there are no linked records)
row policy: "link" must name a link column (internal col_<hex> key)
row policy target: a link leaf may not appear inside a link_target's target (row policies are 1-hop)
row policy nests deeper than the max 5 levels
row policy exceeds max 24 leaves (25)
row policy exceeds max 6 link leaves (7)row policy … 開頭的字串是 Workbench 對 row-policy 文法的本地預檢——節點種類、允許的 key、quantifier 與 require_present 規則、深度(5)、leaf(24)與 link leaf(6)上限、以及 row policy <path> 標籤都與伺服器的 custom_table_row_policy.py 相同,因此本地過檢的 policy 形狀一定是伺服器接受的形狀。預檢會對照文件解析本表每個 scalar leaf 的 column 與每個 link_target 的 link 欄位;link_target 的 target 內的 leaf 屬於被連結的表,由伺服器 plan 解析。Operator、token 與欄位型別的配對同樣由伺服器判定。
身分 token 的語法與解析失敗屬於伺服器 plan 錯誤,目錄請見可攜身分 token。工作台會驗證 principal.id 的 pattern,但無法解析名稱。
前四類模板(audience 兩類、can_insert、access level)目前由工作台本機穩定攔截;server 尚未穩定序列化相同 validator detail。精確來源:components/iac/parse.ts。
動手試試
在 IaC 工作台建立範例 chatroom grant,依序移除 audience、把 can_read 改成 filtered 卻移除 policy,再修正。對測試 scope plan 後,核對 principal、audience、visible columns 與 scope values。