design.md 第 6.4 節(policy 形狀、session 變數)、D12localhost: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 行並逐條核實compliance.projects、public.devices、compliance.evidence_batches、compliance.detection_executions、survey.surveys、compliance.information_systems。排除:public.bulletins(首腦已裁)、compliance.module_frames(本棒建議改排除,待首腦裁,理由見第 8 列)。EXISTS (SELECT 1 FROM compliance.projects p WHERE p.id = 子表.project_id),父表加條件就自動跟著收窄。問卷的子表同理,鏈到 survey.surveys。tenant_id 判,不透過父表;而且有 API 直接以子表 uid 定址。FOR ALL(detection_executions、detection_execution_groups),改成 D12 形狀時連 INSERT 也會被套上同一個條件。建議 migration 拆成 SELECT/UPDATE/DELETE 三條加原本的 INSERT 條件。org_unit_id IS NULL 判。 DEV 的 user_roles 有 141 筆 scope='tenant',其中 140 筆的 org_unit_id 有值。T-3.5/T-3.6 判「這筆是租戶指派」要看 scope 欄,不是看部門欄空不空,否則 140 筆租戶指派會被誤當成部門指派、範圍被收窄。org_units 10 列 path 全有值、全符合 /id/…/ 格式;採用的六張表 org_unit_id 全數有值(module_frames 164 列中 52 列為空)。既有 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 建議排除的理由:
a1-oscal-flow.md 定案第 10 條裁定「資源庫範圍層 tenant」,該份盤點全部資源庫端點的範圍層都標 tenant。資源庫是全租戶共用的範本,再加部門隔離與已裁結論衝突。projects.module_frame_id 指向 org_unit_id 不同的 module_frames 列)。開隔離後,這些專案的規劃頁讀不到自己用的資源庫,且 RLS 擋掉不報錯。workflow_templates 一起改,維護面多一組 SYSTEM/SHARED 三種 scope 的交叉判斷。bulletins 與 module_frames 以外未列入的表:解析任務(oscal.*_parse_jobs 四張,有 org_unit_id)、設定表、工作紀錄照 design 6.4 不挑。
給 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% 一致)。
決策者:這一版部門資料隔離不擋任何資料。 不改任何 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 前要定的:
module_frames 改排除? 建議排除。理由見上表下方三點:資源庫範圍層已裁 tenant、DEV 有 26 個專案跨部門引用資源庫、要完整隔離還得連 workflow_templates 一起改。workflow_executions/job_executions)要不要一起隔離? 建議一起加。這兩張表與專案沒有 FK,policy 只看 tenant_id,「我的任務」這類直接查任務表的列表在專案被隔離後仍會列出別部門專案的任務;以任務 uid 定址的端點(提交、退回、留言)有 service 層專案成員檢查,但列表類沒有。兩表 org_unit_id 全有值且與專案一致,加進來成本是 6 條 policy。不加的代價是「看不到專案,卻看得到它的任務」。design 6.4 寫「不挑子表」,這兩張邏輯上是專案的子表、實際上不鏈父表,所以需要首腦明裁。survey.surveys,問卷快照的部門=專案部門,別部門的受訪者會讀不到題目,且不報錯。建議:policy 另加一條放行路徑「本人是該專案的成員」(專案表與問卷快照各一),否則開隔離的客戶要替每個跨部門成員另開部門指派。此條影響 policy 形狀,需在 T-3.6 開工前定。user_roles.scope='tenant' 的 141 筆中 140 筆 org_unit_id 有值。建議 T-3.5/T-3.6 一律以 scope 欄判,design 6.3「租戶指派為空」的描述要改寫成「scope='tenant'」。oscal.ssp_components.props),讀取時再查主表補名稱(app/oscal/service/ssp_resources_context_service.py:126-158,查不到吞例外回空)。專案引用了別部門的設備或資訊系統時,開隔離後 SSP 頁顯示的名稱會變空白。DEV 目前沒有這種跨部門引用可驗。是否接受,或改成查名稱時走系統放行旗標,請首腦裁。