---
title: FR-094 可見度設計——租戶、階層、部門三條原則
brand: Guidant AI · **FR-094** 租戶隔離修正（RLS 第三波）
eyebrow: FR-094 · 設計決策 · 2026-09-14
h1: 租戶是牆、階層只能往下看、部門不是牆
lede: 決定「誰能看到什麼、誰能改什麼」的三條原則，以及這三條對 13 張表＋4 支 view 開隔離的影響。這份回答「為什麼這樣定」；現況一律看站首頁的總進度表與 `docs/system-design/database/RLS_DESIGN.md`。
chips: [{text: 三原則已定, kind: ok}, {text: B 案定案, kind: ok}, {text: 部門條件要先修, kind: crit}]
footer: 沿革見 handoff LOG 與 git log。
---

::: {.callout .warn}
**🔴 這份是設計決策，不是現況。**

內文的數字與待辦停在 2026-09-14 定稿當時，之後實作改動的結果不回頭改寫這裡。現況看 `README.md` 總進度表與 `RLS_DESIGN.md`。
:::

## 一句話 {#tldr}

租戶是資料歸屬的邊界，一筆資料只屬一個租戶，不跨租戶指派、不跨租戶操作。階層只給往下的唯讀監看，要在某租戶內操作就成為該租戶成員、用切換進去做。部門只記「誰負責改」，不擋讀取，可見度靠角色與參與關係。

## 三條可見度原則 {#principles}

| 原則 | 白話 | 資料庫怎麼落 | 程式怎麼落 |
|---|---|---|---|
| **① 租戶不跨** | 一筆資料只屬於一個租戶。別租戶的人要看，只能靠「原廠公版」「母租戶標記分享給子樹」或「複製一份過去」，不能把人指派進來 | 每張表的查詢規則只認 `super admin` 或「租戶路徑前綴」；公版用 `scope='SYSTEM'`（既有三張用 `is_builtin` 不回改） | 指派人、參與者、審核者的候選名單只列本租戶（含切進來的）帳號 |
| **② 階層只往下看** | 母租戶看得到子租戶的專案、任務、缺失，但不在子租戶裡操作。要操作就由子租戶管理員把人加進來、給角色，此人切到子租戶做事，紀錄留在子租戶 | 前綴比對天生就是「上看下、下看不到上、兄弟互不見」，不加「下看上」的分支 | 一個帳號可隸屬多個租戶（`user_tenants`）、切租戶功能已有；不另做跨租戶指派 |
| **③ 部門不是牆** | 部門是租戶內的歸屬標籤與寫入範圍。誰能看靠角色與參與關係（我被指派、我是專案參與者），跨部門協作是常態 | 查詢規則一律不加部門條件；改、刪規則不綁部門（要收緊走「歸屬部門或管理角色」分支，另案） | 清單頁可用部門當篩選器，不當過濾牆 |

## 業界怎麼定義 {#industry}

多租戶階層的主流做法一致：**階層只給監看，不給操作；要操作就成為那個租戶的成員。**

| 平台 | 上層對下層 | 要在下層操作 |
|---|---|---|
| AWS Organizations | 管理帳號看成員帳號的帳單、合規、稽核紀錄 | 切換角色進成員帳號，紀錄留在成員帳號 |
| Google Cloud 資源階層 | 組織層看全部專案 | 權限往下繼承，不往上索取 |
| Microsoft Dataverse 業務單位 | 上層可讀下層 | 寫入綁資料所屬單位，靠角色取得 |
| Azure Lighthouse | 代管方看客戶訂閱 | 客戶明確授權角色，代管方切到客戶脈絡操作，可隨時收回 |
| Okta／Auth0 多組織 | 母組織管子組織設定 | 使用者是子組織成員，一個帳號可屬多組織 |

部門可見度則是三檔制，**預設共享、例外收緊、收緊靠角色與參與關係**：

| 檔位 | 意思 | 例子 |
|---|---|---|
| 共享可讀、歸屬部門可改 | 公司資產誰都查得到，負責單位改 | ServiceNow CMDB、Jira 專案 |
| 共享可讀、改靠角色 | 部門只是分類 | Salesforce 預設公開讀寫再用角色收 |
| 私有、參與者可見 | 敏感資料逐筆收緊 | Salesforce 私有物件＋分享規則、GRC 產品的稽核證據 |

## 每類資料的檔位 {#data-classes}

| 資料 | 讀 | 改／刪 | 理由 |
|---|---|---|---|
| 設備、資訊系統 | 全租戶 | 歸屬部門或管理角色 | 公司資產清單，稽核要跨部門查 |
| 合規框架、模組框架、檢測方案、流程範本 | 全租戶（含公版） | 歸屬部門或管理角色 | 參考資料，本來就是給大家用 |
| 問卷 | 全租戶（本租戶自建） | 歸屬部門或管理角色 | **沒有公版、不需要公版**：租戶各自建，任務引用時複製進任務。出貨基線零筆問卷；DEV 掛 tenant 1 的 8 份是 2025 年手建測試資料，不是公版 |
| 專案、任務、流程執行 | 全租戶，程式層依角色收成「我參與的」 | 專案參與者、被指派人、管理角色 | 寫入綁參與關係最自然 |
| 問卷作答、證據檔、缺失改善計畫 | 全租戶；日後可逐筆標「機密」收成參與者 | 被指派人、專案參與者 | 真正可能要「特定不能瀏覽」的是這類，用逐筆旗標不用部門 |
| 使用者、角色、部門名冊 | 全租戶 | 管理角色 | 選人、指派都要查 |
| SSP、稽核報告 | 全租戶 | 專案參與者 | 沒有部門欄位，也不需要加 |
| 原廠公版 | 所有租戶 | 平台管理員 | **出貨基線的公版只有三類**（`scripts/init/04`、`05` 實查）：合規框架 CMMC 2.0 L1／L2（`oscal.*`，全域共用無租戶欄位）、流程範本 2 支（`flow_templates.is_builtin`）、掃描設定檔 10 套 TWGCB（`detection_profiles.scope='SYSTEM'`）。模組框架、工作流程範本有公版機制但出貨零筆（`workflow_templates` 基線刻意清空、04 有非空即報錯的守衛）；DEV 上掛 tenant 1 的 59 支 workflow_templates、8 份問卷、1 筆模組框架都是測試資料，不是公版 |

公版的「共用」是隔離規則多開一條「標了公版就放行讀」的門，寫入仍只有平台管理員；子租戶要改就複製一份成自己的再改。新租戶建立時**不複製參考資料**，只 seed 預設部門、管理員角色、儲存設定。

## 對 FR-094 八張卡的影響 {#impact}

| 卡 | 表 | 租戶原則下要不要改規則 | 部門原則下要不要改規則 | 結論 |
|---|---|---|---|---|
| .1a CM-1767 | 排程系統身分 | 不用 | 不用 | 照派 |
| CM-1559 | fail-open 收緊 | 不用 | 不用 | 第二刀（tenant_id 空值不再算 super admin）DEV 可驗；第一刀（無身分直接拒絕）等有可信驗證環境 |
| .2a CM-1768 | `projects`、雲端硬碟串接 | 不用（不支援跨租戶指派，前綴規則即正解） | 不用 | 照派 |
| .2b CM-1769 | `user_roles`／`user_tenants`／`user_org_units` | 不用（卡片已設計本人恆可見） | 不用 | 照派 |
| .2c CM-1770 | `poams`、證據分類、`job_executions`、`framework_parse_jobs` | 不用 | 不用 | .1a＋1559 後派 |
| .2d CM-1771 | `workflow_executions`、4 支 view | 不用 | **要**：既有改、刪規則綁部門且無逃生口，開了跨部門推進任務會更新 0 筆不報錯 | 開之前把改、刪規則的部門條件換成租戶條件 |
| .2e CM-1772 | `workflow_templates` | 不用 | **要**：同上 | 同上 |
| .3a CM-1773 | 問卷 12 張裸表 | 規則錨在任務或問卷主表，不經專案反查 | 不用 | 方案定案後派 |

另外兩件不在八張卡內、由本設計長出來的：

| 事 | 為什麼 | 去向 |
|---|---|---|
| 指派人、參與者候選名單擋掉非本租戶帳號；SPEC 寫明「不支援跨租戶指派」 | 原則①的程式面。不擋，客戶做了會遇到資料靜默消失 | 另開小卡，不擋 1.20.0 |
| 母租戶自訂的模組框架、檢測方案、流程範本、範本庫分享給子樹（`scope` 加一檔） | 原則①的「明確分享」通道。三張既有表現在就擋住母租戶自訂資源，只是沒人踩到 | 另開卡，不擋 1.20.0 |

## 這版做哪幾張：B 案 {#scope}

| 案 | 做哪幾張 | 修到什麼 | 沒修到什麼 |
|---|---|---|---|
| A | .1a＋CM-1559 全部 | 「沒身分就當超級管理員」關掉 | 決策者親測的兩個症狀都還在；1559 第一刀回歸面最大、Drive 路徑 DEV 驗不出、要關的洞落地版目前打不到 |
| **B（採用）** | .1a＋.2a＋.2d＋1559 第二刀 | 親測的兩個症狀修掉；1559 風險小的那半收進 1.1.0 | 11 張表落地版要靠直連資料庫或漏驗歸屬的 API 才打得到，排 1.20.0 之後 |
| C | 八張全做 | 全部隔離 | 五棒重產 schema 與 FR-093 出貨鏈撞、兩支 jedi 套件擠同一次發版、問卷方案要先實查 |

採 B 的判斷：現況一般帳號的可見範圍靠每支查詢各自寫條件撐著，不是牆；三條路現在就打得到（管理員切子租戶、不驗歸屬的任務詳細 API、未來漏寫條件的新 API）。落地版一台機一個客戶，剩下 11 張表洩漏的是同公司內部子租戶的資料，不擋出貨；**SaaS 上線前 C 必須做完**，那時 RLS 是唯一的牆。

## 被排除的選項 {#rejected}

| 選項 | 為什麼不採 |
|---|---|
| 支援「母租戶把任務指派給子租戶帳號」，規則加「參與者可見」讓下看上 | 被指派人要看的東西散在十幾張表（專案、流程、問卷、人名），逐張開洞等於把牆拆掉；操作紀錄會留在別人租戶，責任歸屬不清。業界沒有這種做法，改用「多隸屬＋切租戶」 |
| 部門當第二道前綴牆（部門樹也「上看下」） | DEV 任務指派 68% 跨部門，設備、資訊系統、問卷都是跨部門在用；擋了處處卡。業界部門只當篩選與寫入歸屬 |
| 修改共用 helper `app_tenant_allowed_for_session` 加「下看上」 | 12 張以上的表共用，改它等於全部子租戶看到母租戶全部資料 |
| 用 `can_read_all_orgs` 逃生口補改、刪規則 | 逃生口是程式層在四處手動開的旗標，開在誰身上沒有規範；規則本身不該綁部門，拿掉條件比補逃生口正確 |

## 反悔條件 {#revisit}

| 出現這件事 | 該改什麼 |
|---|---|
| 客戶明確要求「總部的人不加入子公司也能在子公司專案裡操作」 | 重新評估原則②，但仍應以「代管授權」形式（子公司明確授權、可收回、紀錄留子公司）落地，不是開洞 |
| 客戶要求「部門之間互相看不到」 | 用「參與者可見」分支解，不把部門變成前綴牆 |
| 出貨後發現某類資料需要逐筆機密 | 加逐筆旗標（機密＝只有參與者可見），不動部門與租戶規則 |
