建議 model:Sonnet 5,effort:medium——要回填資料、加欄位、改 policy、改讀取端四件事,且「無主凍結副本」的筆數在 DEV 本機與 188 快照不一樣,要實查後判斷。本卡屬 FR-094(母卡見兄弟卡段),.2 開隔離的第 5 棒(整案第 7 棒)。無硬前置,可平行。
工作流程範本表(workflow_templates)混了兩種東西:客戶自己的範本,以及原廠提供的 59 支公版範本(掛在 tenant 1)。隔離規則寫好了但開關關著,而且規則沒有「公版全租戶可讀」的分支——直接打開,客戶會看不到公版。另外每次有人正式啟動稽核流程,系統會把範本複製一份凍結(source_template_uid 非空的那些),FR-087 盤點在 188 快照上發現 191 筆凍結副本沒填所屬租戶——打開隔離它們會從所有客戶眼前消失,其中 132 筆還連著進行中的流程。
首腦核對(2026-09-13 DEV localhost 實查;與 FR-087 於 188 快照的數字並列):
compliance.workflow_templates:RLS 關、4 policy、18,135 筆(188 快照 17,544)。policy:_select 只有 app_tenant_allowed_for_session(tenant_id);_update/_delete 是 tenant AND app_org_allowed_for_session(org_unit_id);_insert 是 tenant AND (can_read_all_orgs OR org)(02-schema.sql:24178-24195)。四條都沒有 is_super_admin 分支、沒有公版分支。tenant_id IS NULL 是 0 筆(188 快照是 191 筆、其中 132 筆有活連結)。tenant 分佈:102 有 18,076、1 有 59(=內建公版)。凍結副本(source_template_uid IS NOT NULL)1,489 筆全在 102。這代表本機 dump 之後有人回填過,或兩份快照本來就不同——本棒要①對 188:25432 的 guidant_ai_dev 快照與基線庫 guidant_ai 各唯讀查一次 NULL 筆數,②不管 DEV 是 0 還是 191,回填 SQL 都要寫進 migration(冪等 WHERE tenant_id IS NULL),因為 STG/POC 可能還有無主列。tenant_id/org_unit_id/source_template_uid 有,無 scope、無 is_builtin(\d compliance.workflow_templates)。D1 裁新開的表一律用 scope。compliance.workflow_executions.template_id → workflow_templates.id(jedi-flow-engine infra/models/workflow_execution.py:41),executions 有 tenant_id。無連結的無主列(188 快照 59 筆)由本棒實查後回寫問首腦「刪或掛 root(tenant 1)」,不自己決定。infra/flow_engine/repository/flow_template_repo_impl.py:29-36(flow_templates 用 or_(tenant_id == x, is_builtin == True) union)。workflow_templates 的讀取端在 jedi-flow-engine(jedi_flow_engine/infra/repository/,先 grep WorkflowTemplate 的 query)與主專案 app/flow_engine/service/workflow_template_snapshot_service.py:46 clone_master_as_snapshot(凍結時要把來源的 tenant_id 寫進副本——這是 191 筆無主的根因,要一起補)。~/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-09-XX-fr094-cm<本卡號>-workflow-templates-scope-backfill-rls.sql (新)本棒 migration
~/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/manifest.tsv 加一列
~/Projects/Billows/Audit-Manager/compliance-manager-be/app/flow_engine/service/workflow_template_snapshot_service.py:46 凍結副本要帶 tenant_id/org_unit_id(根因)
~/Projects/Jedicogy/module/jedi-python-package/jedi-flow-engine/jedi_flow_engine/infra/models/workflow_template.py:12 model 加 scope 欄位(動套件)
~/Projects/Jedicogy/module/jedi-python-package/jedi-flow-engine/jedi_flow_engine/infra/repository/ WorkflowTemplate 查詢加 scope='SYSTEM' union(先 grep)
~/Projects/Billows/Audit-Manager/compliance-manager-be/infra/flow_engine/repository/flow_template_repo_impl.py:29-36 union 寫法樣板(不改)
~/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/init/02-schema.sql:24178-24195 現有 4 policy
guidant_ai_dev 快照/基線庫 guidant_ai,皆唯讀)並貼回卡;無連結的無主列清單(uid、name、created_at)貼回卡問首腦刪或掛 root,這一步等回覆才做④。ALTER TABLE compliance.workflow_templates ADD COLUMN IF NOT EXISTS scope varchar(16) NOT NULL DEFAULT 'TENANT';+UPDATE ... SET scope='SYSTEM' WHERE tenant_id=1 AND source_template_uid IS NULL;(59 筆內建公版;先查 tenant 1 那 59 筆是否確為公版——名稱/provider 看一眼)。CHECK (scope IN ('SYSTEM','TENANT'))。UPDATE compliance.workflow_templates t SET tenant_id = e.tenant_id, org_unit_id = COALESCE(t.org_unit_id, e.org_unit_id) FROM (SELECT DISTINCT ON (template_id) template_id, tenant_id, org_unit_id FROM compliance.workflow_executions ORDER BY template_id, created_at) e WHERE e.template_id = t.id AND t.tenant_id IS NULL;(同一範本被多個租戶的 execution 連到時先查有無,有就停下問)。冪等。UPDATE ... SET tenant_id=1, scope='SYSTEM'(掛 root)或 DELETE(刪);沒回覆前這段先不寫,先 commit ②③⑤⑥。is_super_admin='t' OR scope='SYSTEM' OR app_tenant_allowed_for_session(tenant_id)(select);update/delete 加 is_super_admin OR 但不加 scope 分支(公版只有原廠能改;原廠走 root 路徑);insert 加 is_super_admin OR。ENABLE ROW LEVEL SECURITY。開前 vs 開後對照:模擬 blsit 開前 18,135、開後 18,076+59=18,135(因為公版可讀);模擬子租戶(131 之一)開後只剩 59 公版。scope;查詢端比照 flow_template_repo_impl.py:29-36 用 or_(tenant_id == x, scope == 'SYSTEM')(RLS 開後其實 DB 已濾,這裡的 union 是讓「列公版」的語意在程式碼可讀);clone_master_as_snapshot 凍結時把 caller 租戶的 tenant_id/org_unit_id 寫進副本、scope='TENANT'。