Column visibility:兩種機制,一個 hidden set
欄位可見性有兩個互補機制:table-level column_acl 是全域 hide-map;每筆 grant 的 visible_columns 是 allowlist。伺服器把兩者折疊成一個 effective hidden_columns 集合,所有讀寫入口都以同一集合執行。
機制一:table-level column_acl
curl -X PATCH \
"$BASE_URL/private/module/custom_tables/department/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/column-acl" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"column_acl": {
"col_dddddddd_dddd_4ddd_8ddd_dddddddddddd": { "read": "managers" },
"col_eeeeeeee_eeee_4eee_8eee_eeeeeeeeeeee": { "read": "all" }
}
}'Map key 必須是 live schema 的 internal column key,read 只接受 all 或 managers。對非 manager,標成 managers 的欄位加入 hidden set;manager 的 table-level hidden set 永遠為空。送出空 map 可清除 table-level hide 設定。
這個設定適合「不論從哪種 grant 進來,成本欄都只有管理員可見」的硬底線。端點見 columnAcl.update。
自 2026-07-28 版起,這個寫入在資料表列鎖下合併。兩個併發的 settings PATCH——例如欄位 ACL 儲存撞上 default-permissions 儲存,或 IaC apply 撞上 UI 編輯——以前兩邊都回 200,晚 commit 的無聲蓋掉早的那個 key;對這個設定來說,代表收緊過的欄位 ACL 可能退回全員可見。現在競爭時 PATCH 會等鎖,並可能回併發合約裡可重試的 409;拿到 200 就表示合併真的成立。
機制二:grant visible_columns
{
"user_id": "33333333-3333-4333-8333-333333333333",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"visible_columns": [
"col_aaaaaaaa_aaaa_4aaa_8aaa_aaaaaaaaaaaa",
"col_bbbbbbbb_bbbb_4bbb_8bbb_bbbbbbbbbbbb",
"col_dddddddd_dddd_4ddd_8ddd_dddddddddddd"
]
}visible_columns 是 allowlist:所有未列出的 live columns 都加入 hidden set。它可放在 user、department、chatroom 與 client grants;省略或 null 代表 grant 本身不縮小欄位,仍會套 table-level column_acl。空 list 會被拒絕,不用它表達「看不到任何欄位」。Table defaults 沒有 visible_columns。
Note
visible_columns只接受 internal keys。這讓欄位 rename 後 grant 仍穩定;建立 grant UI 時,請從 schema 顯示名稱映射到 internal key。
合併公式
hidden_columns
= column_acl 中 read=managers 的 keys(非 manager)
∪(所有 live schema keys − grant.visible_columns)假設 live columns 是 name、status、cost、internal_note:
| 來源 | 結果 |
|---|---|
column_acl 把 cost 設為 managers-only | hide cost |
Grant allowlist 是 name, status, cost | hide internal_note |
| Effective union | hide cost, internal_note |
Allowlist 不能重新打開 table-level hide;即使列出 cost,非 manager 仍看不到。Manager 不需要也不能有 explicit grant,因此不會被 grant allowlist 縮小。
Hidden 代表 absent,不是 null
Record response 會完全移除 hidden field,而不是回傳 null。Filter、sort、search、aggregate 或 write 若命中 hidden key,伺服器會回與真正不存在的欄位相同的 400 Column '<name>' does not exist in table schema,因此 record 相關介面不存在「存在但看不到」的探測管道。Exports、views、history 與依賴欄位的讀取也必須沿用伺服器結果,前端不應自行補回 schema 欄位。
被隱藏的是「值」,不是「名稱」。 只有 manager 能讀的欄位,其名稱仍會出現在 schema_definition.columns 中,被扣住的只有值。Hidden set 是套用在準備好的 record 字典上,computed 欄位則只是被標註 "restricted": true;兩條路徑都不會裁剪 schema 清單。切勿從欄位是否出現在 schema 推論可讀性:這麼做的前端會渲染出一個永遠沒有值的欄位,或送出一個必然 4xx 的 filter。
因此,以下程式會把「不存在」視為正確狀態:
const hasCost = Object.prototype.hasOwnProperty.call(record.data, '成本')
if (hasCost) {
renderMoney(record.data['成本'])
}不要用 record.data['成本'] ?? 0,那會把「無權限」錯誤呈現成真實的零值。
兩個機制合併後如何參與 grant resolution,見 effective permissions。