FR-132 A.1 — D12 部門資料隔離:挑選表清單

D12 部門資料隔離:挑選表清單

  • 卡:T-1.6(CM-2516)
  • 依據:design.md 第 6.4 節(policy 形狀、session 變數)、D12
  • 資料來源:DEV localhost:5432/guidant_ai_dev(2026-10-06 實查 pg_policy、pg_constraint、information_schema);子表 API 由 api/ 與 jedi 套件 api/routing.py grep;證據、檢測、問卷三組的子表觀察引用 a1-detection-evidence-survey.md 第 384–394 行並逐條核實
  • 用途:A.2 寫 D12 policy migration(T-3.6)的輸入
§1

先看這幾點

  1. 六張採用、兩張排除。 採用:compliance.projects、public.devices、compliance.evidence_batches、compliance.detection_executions、survey.surveys、compliance.information_systems。排除:public.bulletins(首腦已裁)、compliance.module_frames(本棒建議改排除,待首腦裁,理由見第 8 列)。
  2. 專案底下十二張子表不用動。 它們的 SELECT policy 全是 EXISTS (SELECT 1 FROM compliance.projects p WHERE p.id = 子表.project_id),父表加條件就自動跟著收窄。問卷的子表同理,鏈到 survey.surveys。
  3. 證據、檢測兩組的子表要一起改。 子表各自只用 tenant_id 判,不透過父表;而且有 API 直接以子表 uid 定址。
  4. 兩張表的 policy 是 FOR ALL(detection_executions、detection_execution_groups),改成 D12 形狀時連 INSERT 也會被套上同一個條件。建議 migration 拆成 SELECT/UPDATE/DELETE 三條加原本的 INSERT 條件。
  5. 「租戶指派」不能用 org_unit_id IS NULL 判。 DEV 的 user_roles 有 141 筆 scope='tenant',其中 140 筆的 org_unit_id 有值。T-3.5/T-3.6 判「這筆是租戶指派」要看 scope 欄,不是看部門欄空不空,否則 140 筆租戶指派會被誤當成部門指派、範圍被收窄。
  6. 前置條件在 DEV 成立:org_units 10 列 path 全有值、全符合 /id/…/ 格式;採用的六張表 org_unit_id 全數有值(module_frames 164 列中 52 列為空)。
§2

逐表結論

既有 policy 形狀簡寫:T = app.is_super_admin 為真 或 app_tenant_allowed_for_session(tenant_id);E(父) = app.is_super_admin 為真 或 EXISTS (父表該列)。

表 採用/排除 org_unit_id 父表 RLS 形狀 子表(鏈父表/獨立 tenant_id) 直接定址子表的 API 結論:要改哪幾張 policy
compliance.projects 採用 有(1,218 列全有值) select/update/delete=T;insert=app_tenant_allowed_for_session 鏈父表 12 張:project_participants、project_control_participants、project_group_participants、control_group_participants、process_participants、project_assessment_plan_mapping、project_org_unit_mapping、project_system_characteristic_mapping、project_summary_reports、task_assignees、project_audit_rounds、review_marks(後兩張無 FK,以 project_id 欄鏈) 有:/audit-round/<rid>/* 約 20 支、/project-summary-report/<uid>、/ssp/<ssp_uid>/*、/ap/<ap_uid>/*、/job-executions/<je>/comments、/flow-engine/task/complete/<id> projects_select、projects_update、projects_delete 三條。子表自動跟著收。SSP、AP 不在 RLS 內(oscal 表沒開 RLS),但 service 層反查專案時走 projects(domain/oscal/service/ssp_project_resolver.py:93),專案看不到就找不到;任務類見待裁第 2 條
public.devices 採用 有(9 列全有值) select/update/delete=T;insert=租戶 且(can_read_all_orgs 或所屬部門) 無直接子表。job_execution_devices(FK,未開 RLS,DEV 0 列)、survey.task_surveys.device_id(無 FK)只存 id 無(jedi_asset/api/routing.py 全部以設備本身 uid 定址) devices_select、devices_update、devices_delete 三條
compliance.evidence_batches 採用 有(20 列全有值) select/update/delete=T;insert=T 獨立 tenant_id 兩張:evidence_batch_files、evidence_classification_runs(兩張都有 org_unit_id;DEV 與父表一致 193/25 列;runs 另有 8 列 batch_id 為空,部門欄仍有值) 有:/evidence-batches/<b>/files*(寫)、/classification-run/<r>/state、/classification-run/<r>/file/<f>/preview、/classification-run/<r>/report/*(jedi_evidence_classification/api/routing.py:40-52) 父表三條+evidence_batch_files_select/update/delete+evidence_classification_runs_select/update/delete,共 9 條。子表用自己的 org_unit_id 判,不改成 EXISTS (父表):runs 有 8 列沒有父批次,鏈父表會讓它們整批消失
compliance.detection_executions 採用 有(76 列全有值) 單一 FOR ALL policy=T(detection_executions_tenant_isolation) 獨立 tenant_id 一張:detection_execution_groups(群組是上層,執行以 group_uid 掛在群組下;有 org_unit_id,DEV 與任務一致)。兩表無 FK 有:/detection-tools/execution-groups/<g>、.../<g>/cancel、.../<g>/assignments/<a>/rerun|cancel|start-now、/detection-tools/executions/<uid>、/detection-tools/jobs/<job_uid>/executions(jedi_detection/api/routing.py:124-162) detection_executions_tenant_isolation、detection_execution_groups_tenant_isolation 兩條 FOR ALL,建議各拆成 SELECT/UPDATE/DELETE 三條(套 D12)+一條 INSERT(維持 T)
survey.surveys 採用 有(52 列全有值,含 30 列 is_snapshot 快照) select/update/delete=T;insert=租戶 且(can_read_all_orgs 或所屬部門) 鏈父表:survey_pages、surveys_trans、survey_discussions→surveys;survey_questions→survey_pages;question_answers→survey_questions→survey_pages。另一條線:task_surveys→compliance.workflow_executions;question_answer_histories→task_surveys 有:/survey-page/<uid>、/survey-question/<uid>、/survey-discussion/<uid>、/project-survey/answers/<uid>*、/task-survey/<uid>(jedi_survey) surveys_select、surveys_update、surveys_delete 三條。題目、頁面、討論、答案自動跟著收。受訪者填答會受影響,見待裁第 3 條
compliance.information_systems 採用 有(96 列全有值) select/update/delete/insert 各一條=T(本表 FORCE ROW LEVEL SECURITY) 無 FK 子表。專案 SSP 的 oscal.ssp_components 以 props.system_characteristic_id 軟參照 id 無(jedi_asset/api/routing.py 四條皆以資訊系統本身定址) information_systems_select、information_systems_update、information_systems_delete 三條
public.bulletins 排除(首腦已裁) 有(3 列) select/update/delete=T;insert=租戶 且所屬部門 bulletin_org_units(投遞對象,未開 RLS) — 不改。公告已有「投遞給哪些部門」的規則,再疊部門隔離,「發給全公司的公告」會被發文部門的隔離擋住,兩套規則互相打架
compliance.module_frames 首腦初裁採用;本棒建議改排除,待首腦裁 有(164 列中 112 列有值,52 列為空) select=scope='SYSTEM' 或 T 或(scope='SHARED' 且祖先租戶);update/delete=T(排除 SYSTEM) module_frames_trans(FK,未開 RLS);查核項目 BPMN 在 compliance.workflow_templates(有 org_unit_id、自己的 scope policy,不鏈 module_frames) 有:/module-frame/item/<uid>、/module-frame/item/xml/<uid> 直接以 workflow_templates uid 讀(app/module_frame/service/module_frame_item_service.py:31);/flow-engine/process-definition/<uid> 同 若採用:module_frames_select/update/delete 三條,而且 workflow_templates 也要一起改,否則查核項目內容仍可直接讀到。建議排除,理由見下

module_frames 建議排除的理由:

  1. 決策者已在 a1-oscal-flow.md 定案第 10 條裁定「資源庫範圍層 tenant」,該份盤點全部資源庫端點的範圍層都標 tenant。資源庫是全租戶共用的範本,再加部門隔離與已裁結論衝突。
  2. DEV 有 26 個專案引用了「部門不同」的資源庫(projects.module_frame_id 指向 org_unit_id 不同的 module_frames 列)。開隔離後,這些專案的規劃頁讀不到自己用的資源庫,且 RLS 擋掉不報錯。
  3. 要隔離得完整,還要連 workflow_templates 一起改,維護面多一組 SYSTEM/SHARED 三種 scope 的交叉判斷。

bulletins 與 module_frames 以外未列入的表:解析任務(oscal.*_parse_jobs 四張,有 org_unit_id)、設定表、工作紀錄照 design 6.4 不挑。

§3

隔離開關開啟時要動的 policy 清單

給 T-3.6 寫 migration。每條改成 design 6.4 的形狀:既有條件 且(app.org_unit_isolation 不為真 或 app.can_read_all_orgs 為真 或 該列 org_unit_id 為空 或 該列部門 path 落在 app.allowed_org_unit_paths 子樹內);app.is_super_admin 那段 OR 保留在最外層。

# 表 policy 指令 備註
1 compliance.projects projects_select SELECT
2 同上 projects_update UPDATE USING 與 WITH CHECK 都要改
3 同上 projects_delete DELETE
4 public.devices devices_select SELECT
5 同上 devices_update UPDATE
6 同上 devices_delete DELETE
7 compliance.evidence_batches evidence_batches_select SELECT
8 同上 evidence_batches_update UPDATE
9 同上 evidence_batches_delete DELETE
10 compliance.evidence_batch_files evidence_batch_files_select SELECT 子表用自己的 org_unit_id
11 同上 evidence_batch_files_update UPDATE
12 同上 evidence_batch_files_delete DELETE
13 compliance.evidence_classification_runs evidence_classification_runs_select SELECT 子表用自己的 org_unit_id;有 8 列無父批次
14 同上 evidence_classification_runs_update UPDATE
15 同上 evidence_classification_runs_delete DELETE
16 compliance.detection_executions detection_executions_tenant_isolation ALL 建議拆 SELECT/UPDATE/DELETE+INSERT
17 compliance.detection_execution_groups detection_execution_groups_tenant_isolation ALL 同上
18 survey.surveys surveys_select SELECT 子表鏈父表,自動跟著收
19 同上 surveys_update UPDATE
20 同上 surveys_delete DELETE
21 compliance.information_systems information_systems_select SELECT
22 同上 information_systems_update UPDATE
23 同上 information_systems_delete DELETE

合計 9 張表、23 條 policy(其中第 16、17 兩條拆開後實際變成 8 條)。INSERT policy 一律不動:既有 INSERT 看的是使用者所屬部門(app.allowed_org_paths),與 D12 的指派部門不同,design 6.4 已定不變。

若首腦裁定保留 module_frames,追加:module_frames_select、module_frames_update、module_frames_delete,以及 compliance.workflow_templates 的 SELECT/UPDATE/DELETE(先查該表 policy 名稱)。

若首腦裁定任務也要隔離(待裁第 2 條),追加:compliance.workflow_executions、compliance.job_executions 各自的 select/update/delete(兩表皆有 org_unit_id,DEV 與所屬專案 100% 一致)。

§4

裁示(2026-10-06)

決策者:這一版部門資料隔離不擋任何資料。 不改任何 RLS policy、不做隔離開關與預覽頁、不做跨部門成員放行。本檔與 design §6.4 留作下一版開 D12 時的輸入;下面五條在那時一併定。延後的卡見 a1-capability-tree.md 附錄 D 末段。

首腦已裁(下一版直接沿用):module_frames 排除(資源庫已裁 tenant 層、DEV 有 26 個跨部門引用);任務表 workflow_executions/job_executions 要一起隔離(否則看不到專案卻看得到它的任務);「租戶指派」一律以 user_roles.scope='tenant' 判,不看 org_unit_id 空不空,design 6.3 措辭要改。

下一版開 D12 前要定的:

  1. module_frames 改排除? 建議排除。理由見上表下方三點:資源庫範圍層已裁 tenant、DEV 有 26 個專案跨部門引用資源庫、要完整隔離還得連 workflow_templates 一起改。
  2. 任務(workflow_executions/job_executions)要不要一起隔離? 建議一起加。這兩張表與專案沒有 FK,policy 只看 tenant_id,「我的任務」這類直接查任務表的列表在專案被隔離後仍會列出別部門專案的任務;以任務 uid 定址的端點(提交、退回、留言)有 service 層專案成員檢查,但列表類沒有。兩表 org_unit_id 全有值且與專案一致,加進來成本是 6 條 policy。不加的代價是「看不到專案,卻看得到它的任務」。design 6.4 寫「不挑子表」,這兩張邏輯上是專案的子表、實際上不鏈父表,所以需要首腦明裁。
  3. 跨部門的專案成員與受訪者會被擋。 用 DEV 現有指派粗估:3,634 筆專案成員裡有 333 筆,本人既沒有租戶指派、也沒有涵蓋該專案部門的部門指派,開隔離後會看不到自己參與的專案。問卷同理:任務問卷的題目鏈到 survey.surveys,問卷快照的部門=專案部門,別部門的受訪者會讀不到題目,且不報錯。建議:policy 另加一條放行路徑「本人是該專案的成員」(專案表與問卷快照各一),否則開隔離的客戶要替每個跨部門成員另開部門指派。此條影響 policy 形狀,需在 T-3.6 開工前定。
  4. 「租戶指派」的判法:DEV user_roles.scope='tenant' 的 141 筆中 140 筆 org_unit_id 有值。建議 T-3.5/T-3.6 一律以 scope 欄判,design 6.3「租戶指派為空」的描述要改寫成「scope='tenant'」。
  5. 軟參照的名稱會靜默消失。 專案 SSP 的設備、資訊系統以 id 軟參照(oscal.ssp_components.props),讀取時再查主表補名稱(app/oscal/service/ssp_resources_context_service.py:126-158,查不到吞例外回空)。專案引用了別部門的設備或資訊系統時,開隔離後 SSP 頁顯示的名稱會變空白。DEV 目前沒有這種跨部門引用可驗。是否接受,或改成查名稱時走系統放行旗標,請首腦裁。