Skip to Content
核心概念範圍模型

範圍、掛載與跨範圍存取

自訂資料表的「範圍」同時決定三件事:表由誰擁有、URL 掛在哪裡,以及請求先經過哪一層身分驗證。先判斷範圍,再組 URL;不要只拿到 table_id 就猜一條通用路徑。

三種擁有範圍

範圍一般掛載前綴適合的資料
聊天室/private/module/custom_tables/chatroom/{chatroom_id}只屬於一個協作室的工作資料
部門/private/module/custom_tables/department/{department_id}由部門維護、可分享給多個聊天室的系統資料
公司/private/module/custom_tables/company全公司的共用資料;路徑本來就沒有 company_id

例如,讀取一張聊天室表是:

GET /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222

同一個 table_id 若屬於部門,必須改走該部門的掛載:

GET /private/module/custom_tables/department/33333333-3333-4333-8333-333333333333/tables/22222222-2222-4222-8222-222222222222

重點

分享只增加存取權,不會搬動或複製資料表。部門表分享給聊天室後,資料列 API 仍走 department/{department_id},不是改走 chatroom/{chatroom_id}。

掛載規則與無範圍 ID 的路徑

大多數端點共用相同的尾段,例如 /tables/{table_id}/records,只替換前面的範圍掛載。後端實際上把聊天室、部門、公司三套路由分別掛到 /chatroom、/department、/company,再由共用處理函式執行資料操作。要看哪個 family 掛在哪個 scope——包含 commands、附件、public-read tokens、publish sessions,以及那一條共用 ETL chunk 路徑——請從範圍與掛載路徑開始。

有幾組刻意採用「無範圍 ID」路徑:

  • 附件端點——multipart init、complete、abort、status 與下載——使用 /{scopeName}/tables/{table_id}/...;聊天室與部門路徑都不帶 chatroom_id 或 department_id。詳見附件 Blob。
  • Public-read token 管理使用 /{scopeName}/tables/{table_id}/public-read-tokens[/{token_id}]。
  • 背景遷移狀態與診斷結果使用 /{scopeName}/migrations/{migration_id}/status 與 .../diagnosis-results;它們不帶範圍 ID,也不帶 table_id。詳見遷移與診斷。

/shared-with-me 與 /tables/resolve 完全沒有 scope 前綴。反過來,公司的 commands(/company/{company_id}/commands…)與能力探索路由(/company/{company_id}/core/capabilities)則會帶公司 id。

請照慣例使用擁有範圍的掛載,但伺服器並非處處檢查它:附件與 public-read token 路由從 table_id 解析範圍、忽略前綴;聊天室與部門的 /migrations/{migration_id}/… 路由會依遷移所屬的表授權,不論那張表在哪個範圍——只有 /company/migrations/… 路由綁定公司範圍的表。伺服器一律從表或全域唯一的 migration ID 找回擁有範圍並重新驗證呼叫者,所以這些都不是繞過範圍。遷移狀態的所有拒絕——對表沒有權利、門檻拒絕、其他公司、表在垃圾桶,或 company 路由上的非公司範圍表——都回 404 {"detail": "Migration not found"}。

驗證不是三個範圍都一樣

路徑尾段相同,不代表所需權限相同。常見的驗證層次如下:

操作層次聊天室部門公司
列出/建立資料表ChatRoomJoinedRequired(列出)或 ChatRoomAccessRequired(建立)DepartmentAccessRequired;一般成員改用 readable-tables(DepartmentTenantRequired)CompanyAccessRequired
By-id 資料表中繼資料(詳細資料、/columns、/permissions/me、/moderators、/rules、/triggers、/staged-changes、/migration/status)詳細資料、/columns、/migration/status:ChatRoomJoinedRequired + ChatroomTableMetadataReadRequired;/permissions/me:ChatRoomJoinedRequired + ChatroomTablePermissionsReadRequired;/rules、/staged-changes:只有 ChatroomTableMetadataReadRequired;/triggers:ChatroomTableTriggerConfigReadRequired;沒有 /moderators 路由DepartmentTableMetadataReadRequired(/triggers:DepartmentTableTriggerConfigReadRequired)CompanyTableMetadataReadRequired(/triggers:CompanyTableTriggerConfigReadRequired)
讀資料列CustomTableReadRequiredCustomTableReadRequiredCustomTableReadRequired
新增資料列CustomTableInsertRequiredCustomTableInsertRequiredCustomTableInsertRequired
編輯/刪除資料列CustomTableEditRequiredCustomTableEditRequiredCustomTableEditRequired
任一寫入能力(附件共用 router)CustomTableWriteRequiredCustomTableWriteRequiredCustomTableWriteRequired

驗證會先確認使用者能進入該擁有範圍,再依表管理者、使用者、部門、聊天室、預設 grant 的順序解析出有效的 can_read、can_insert、can_edit、列過濾與隱藏欄位——這是一道停在第一個命中來源的階梯,不是跨來源的聯集。只有聊天室那一步會摺疊:它合併呼叫者各房間所有存活的 internal audience grant(每個軸取最高層級、不同的 filter 以 OR 串接、can_insert 取 OR),而對範圍內的呼叫者,還會再逐軸與資料表預設的基準合併:只有分享的層級嚴格更寬時才採用它(同級時保留基準與其 filter),can_insert 取 OR;若讀取軸由基準勝出,分享帶的 visible_columns 白名單會被捨棄,從分享取得的編輯層級也一併退回基準。精確順序見 effective permissions。中繼資料門檻對同公司但沒有權利的呼叫者——以及掛錯範圍的請求——都回與 id 不存在相同的 404 {"detail": "CustomTable Not found"}(chatroom 的 /permissions/me 為 Table not found),所以那裡的 404 無法分辨「掛錯範圍」與「沒有權利」。相同 endpoint 在三個範圍也可能有不同管理門檻:解析連結是已知會分歧的操作(ChatRoomJoinedRequired/DepartmentTenantRequired/任何已登入使用者),匯入的 extract/status 也不同。新增欄位與資料表診斷過去也會分歧;自 #1748 起三個範圍都需要 CustomTableModeratorRequired。整合時應以各張參考卡片的 scope/auth matrix 為準,不要從 URL 推論角色。

第一列的 *AccessRequired 閘門只看是否為建立者或角色,不看任何資料表授權。ChatRoomAccessRequired 放行房間建立者、房間所屬部門的管理者(role >= CAN_MANAGE_DEPARTMENT,預設 2)或公司管理者(role >= CAN_MANAGE_COMPANY,預設 3),光是房間成員並不夠;其他人若來自別的部門會收到 403 Insufficient permissions, not at the same department. (Required company manager),來自房間所屬部門則收到 403 Insufficient permissions, not the creator. (Required department manager)(房間不存在或已刪除時為 404 Chatroom Not found,自後端 5.21.0 起其他公司的房間也是:Custom Tables 的房間閘門對外來房間的回應與不存在的房間完全相同,見其他公司的 id 回應與不存在相同)。DepartmentAccessRequired 只放行該部門的管理者或公司管理者,其他人一律 403 Insufficient permissions for department-level access.;CompanyAccessRequired 對角色低於 CAN_MANAGE_COMPANY 的呼叫者回 403 Insufficient permissions for company-level access.。這三道閘門同樣守著各範圍的資料表標籤、垃圾桶清單、bootstrap、IaC 與標籤 purge 路由。DepartmentTenantRequired(部門的 readable-tables、commands 與能力探索路由)與 CompanyTenantRequired(/company/{company_id}/… 的 commands 與能力探索路由)完全不看角色,只確認路徑中的 id 屬於你的公司:部門不存在或屬於其他公司時回 404 Department not found(DepartmentAccessRequired 也一樣),company_id 不是你自己的公司時回 404 Company not found。

其他公司的 id 回應與不存在相同

自後端 5.21.0 起,屬於其他公司的 Custom Tables id,無法與從未存在的 id 區分。伺服器先檢查租戶——在任何角色、成員資格、授權或房間檢查之前——所屬公司無法解析的 id 也以同樣方式拒絕。回應一律是該類 id 自己的「id 不存在」答案,所以狀態碼與 body 都不會透露這個 id 在別處存在:

你送出的外來或不存在的 id 得到
資料列、歷史、匯出、檔案匯入、附件、欄位、規則、trigger、view、public-read token、moderator、purge 或 migration 狀態路由上的 table_id,或資料表本身404 {"detail": "CustomTable Not found"}。by-id 中繼資料讀取保留各自的「id 不存在」文字:聊天室的 /permissions/me 回 {"detail": "Table not found"}
匯入 v2 路由(…/tables/{table_id}/import-v2/… 與 …/tables/{table_id}/import-mappings/{name})上的 table_id404 {"detail": "Table not found"}
子集合(child-collection)或 bundle 路由(…/tables/{table_id}/child-collections、…/tables/{table_id}/records/bundle… 與 …/tables/{table_id}/records/{record_id}/bundle…)上的 table_id404 {"detail": "Custom table not found"}
聊天室的資料表、資料列、欄位、規則、trigger、view、IaC、檔案匯入、events、input 或 package 路由(…/chatroom/{chatroom_id}/…)、聊天室 command 或 composer 路由、聊天室能力探索上的 chatroom_id404 {"detail": "Chatroom Not found"},與房間不存在或已刪除的回應相同
上面的匯入 v2、子集合與 bundle 路由,以及 …/tables/{table_id}/period-events 與 …/columns/{column_id}/required/preview 上,外來或不存在的 chatroom_id不是 Chatroom Not found:這些路由是在該房間之下查找資料表,而不是先檢查房間,所以回的是該路由自己的資料表 404——Table not found(匯入 v2、period-events 與 required/preview)、Custom table not found(子集合與 bundle)
link 欄位的 target_table_id(新增欄位、欄位 preview、建立資料表)400 {"detail": "Link target table not found"}。欄位 preview 把同樣的文字作為 target_table_id 上的 valid: false 錯誤回報;聊天室範圍建立資料表時前面會加上 Failed to create table with moderator assignment:
GET …/migrations/{migration_id}/diagnosis-results 的 migration_id404 {"detail": "Diagnosis results not found"},報告不存在、尚未完成或已過期時也是這個答案
trigger 的 create_record、update_record 或 materialize_slots action 的 table_id400 {"detail": "target table not found or not accessible"},發生在 approval_trigger_conflict 有機會點名目標之前
GET /public/task/{ticket_id} 上的私有 ticket404 {"detail": "Task not found"};見輪詢 ticket
個別使用者授權路由(…/tables/{table_id}/permissions:list、grant、bulk、revoke)上的 table_id不是 404:403 {"detail": "Only table managers (creator, scope manager, or table moderator) can manage permissions."}——不是管理者的呼叫者、不存在的 id、其他公司的資料表,都得到這同一個答案

除了其他公司的情況,來自資料表所屬公司的呼叫者還有一處改變:上面聊天室那一列的 …/chatroom/{chatroom_id}/… 路由上,路徑中不存在或已刪除的 chatroom_id 現在在任何資料表閘門執行之前就回 404 Chatroom Not found,即使只憑資料表閘門本來會放行該呼叫。其餘不變:來自資料表所屬公司、但缺少權利的呼叫者,仍拿到原本的 403 與該閘門自己的文字(資料列閘門、moderator 閘門、範圍門檻);同公司、對資料表完全沒有任何權利的呼叫者,仍拿到 by-id 中繼資料讀取的隱藏資料表 404。在使用者換到其他公司之後仍留存的 moderator 或房間管理者資料列不再授權任何事,因為租戶檢查發生在它之前。

  • 把 404 視為「對你而言不存在」:不要重試,也不要把 403 與 404 的差別當成 id 在其他租戶存在的證據——伺服器已不再給出這個訊號。
  • 資料表 id 與聊天室 id 不要再依 403 Insufficient permissions. (Different Company) 分支;資料表 id 的門與 Custom Tables 的房間閘門已不再送出它。

部門介面不接受房間參數

部門表可以同時授權給多個聊天室。過去有一組 *Scoped dependency 變體,會讀取 ?acting_chatroom_id 這個 query parameter 來決定請求代表哪個房間;這五個變體已於 2026-07-29 全部刪除,該參數也從全部 57 個 custom-table 操作上移除。上表中的閘門名稱是這幾個層級現在僅存的版本;*Scoped 變體已完全不存在。

伺服器改為從呼叫者本身解析 channel floor:在 REST 上表管理者豁免;其他人拿到的是「所屬、存活、且對該表持有 internal grant 的每個聊天室」的 scope_values 聯集。(綁定房間的 carrier——agent session、外部 client、由 agent 發起的 command 執行——即使是表管理者,也只限該房間的範圍;trigger 以 invoke_command 執行聊天室範圍的 command 時,會在該 command 的房間中執行;透過 REST 手動執行的 command /execute 與 /query,以及 trigger 以 invoke_command 執行部門或公司範圍的 command,都不帶房間,依 REST 規則處理。)被兩個房間授權的成員,一次請求就讀到兩個房間的範圍——這也是為什麼資料表清單與詳情頁的 record_count 就是這個聯集計數,以及為什麼多房間成員在詳情頁會拿到 200,而不再是過去的 400。

Warning

仍然送 ?acting_chatroom_id=<id> 不會報錯,也不是你察覺得到的 no-op:FastAPI 會丟棄未知的 query parameter,所以請求照樣 200,而你指名的房間根本不被採用。仍在釘選房間的用戶端,其實靜默地拿到了聯集。請把參數從呼叫端移除;不會有任何訊息告訴你它失效了。

沒有任何合格房間的呼叫者,不會被要求去指定一個。讀取回 200 零列或 uniform 404,只有寫入會被 403 {"detail": {"error": "scp_scope_undeclared", "message": "no chatroom of yours declares a scope on this table", "table_id": "<table_id>", "rule_id": "<rule_id>"}} 拒絕——請依 detail.error 分支。完整 floor 行為見 SCP;agent session 的不對稱見 union 契約。

清單上的 record_count 現在與詳情計數用同一套底線

資料表清單上的 record_count——GET .../chatroom/{chatroom_id}/tables 以及走同一個 helper 的部門與公司版本——過去是該表未經過濾的存活列數。授權為 own 或 filtered 的成員因此會看到一個涵蓋自己打不開的資料列的數字;這是一種雖小但真實的揭露:它等於告訴對方 policy 後面藏了幾列。自 backend PR #1172 起,清單改為套用與詳情計數完全相同的條件,並逐表解析:

呼叫者在該表上的身分record_count
表管理者,或 can_read: "all" 且沒有 SCP 收窄完整存活列數——不變,而且該頁上所有這類表仍然由單一批次查詢一次算完
can_read: "own"自己建立的列;沒有建立者的列仍然看不到
can_read: "filtered"編譯後的 row policy:無法解析的 leaf 依 and/or/not 極性 fail closed,只有整棵樹都失敗時才計為 0;SQL lane 無法編譯的 leaf(numeric、boolean 或日期欄位上的 contains,或格式錯誤的 decimal operand)則會直接拋錯,所以清單回的是 500 而不是筆數
受 SCP 規則管轄、但沒有任何被授權房間0,與被遮蔽的詳情讀取一致

由此得到兩件事。清單上的計數是逐呼叫者的數字:同一個房間、兩個人看到不同的值是合理的,用戶端絕不可以把某位使用者的清單快取給另一位使用者。另外,SCP 會自己逐表重新判定管理者身分,而不是從權限階梯上讀取,所以 SCP 判定豁免的呼叫者,即使階梯會收窄,也仍然拿到完整計數。

跨範圍 ACL 路由

跨範圍分享的端點永遠掛在「表的擁有範圍」,共有三種形狀:

目的擁有表路由族群
讓一個部門存取聊天室表聊天室/chatroom/{chatroom_id}/tables/{table_id}/department-permissions/{department_id}
讓一個聊天室/audience 存取部門表部門/department/{department_id}/tables/{table_id}/chatroom-permissions/{chatroom_id}
讓另一個部門存取部門表或公司表部門或公司/department/{department_id}/tables/{table_id}/department-permissions/{target_department_id}(公司:/company/tables/{table_id}/department-permissions/{target_department_id}),catalog 為 departmentTableGrants.*

部門表也可以用 default_permissions.audience: "company" 一次開放給所有部門。

第一組 departmentPermissions.* 只存在聊天室範圍;第二組 chatroomPermissions.* 只存在部門範圍;部門表與公司表上同樣形狀的 department-permissions URL 則是另一組 departmentTableGrants.*。聊天室成員可用 GET .../chatroom/{chatroom_id}/tables/shared 發現已授權給該聊天室的部門表(完全沒有權利的表會被略過),部門成員則用 GET .../department/{department_id}/readable-tables 列出自己部門讀得到的表;之後仍用回傳表的部門掛載讀寫。

Grant 可以縮限列權限與可見欄位,但不會被 Insight system 開關取代;該開關從不授予存取權,ACL 仍是每次 API 與資料載入的安全邊界。

組路徑前的檢查順序

  1. 從表回應的 chatroom_id、department_id、company_id 判斷擁有範圍。
  2. 選用該範圍的掛載與 endpoint 參考卡片。
  3. 受治理的部門表:呼叫者的房間未宣告 scope 時,預期看到空頁而不是錯誤——而且永遠不要送 acting_chatroom_id。
  4. 跨範圍分享後保留原擁有範圍,只更新 grant 與本地的可用表清單。

可在 API Playground 選擇 scope,確認實際展開的 URL 與 auth 說明;完整 grant 端點見權限參考。

Last updated on