diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 5c823634..a7198eb2 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -2884,3 +2884,18 @@ - 防复发:账号重置只允许使用普通管理员 mutation guard;共享高风险 guard 不做全局放松,并由合同测试锁定角色变更仍需邮箱复核。 - 相关记录:无 - 修复版本:本次 staging 候选 + +## BUG-168 | 生时校正运行时开关被 RLS 静默隐藏并误报服务未开放 + +- 状态:resolved(本地回归已通过,待 staging 迁移与真实接口验收) +- 首次发现:2026-08-11 +- 最近更新:2026-08-11 +- 影响面:`POST /api/rectification/agent`、共享 `loadRuntimeFeatureFlags` 的运行时开关读取,以及管理端 feature flag 列表。 +- 用户现象:staging 的 V9 生时校正接口返回 `503 rectification_runtime_disabled`,即使数据库中的 `rectification_runtime_version` 已是 `published`、`enabled=true`、`rollout_percentage=100`。 +- 触发条件:self-hosted Web 通过 `ADMIN_DATABASE_URL` 以 `admin_runtime` 查询启用了 RLS 的 `public.feature_flags`。 +- 根因:`20260806050000_operations_feature_flags.sql` 给 `admin_runtime` 授予了表级 SELECT,但启用 RLS 后没有创建对应 SELECT policy。PostgreSQL 因此不报权限错误而是返回零行;`loadRuntimeFeatureFlags` 将缺失记录安全降级为 disabled,路由遂返回“生时校正服务暂未开放”。 +- 修复:新增向前迁移 `20260811030000_feature_flags_admin_runtime_read_policy.sql`,保留最小 SELECT grant,并为 `admin_runtime` 创建 `feature_flags_admin_read` RLS SELECT policy;不放宽匿名、普通用户或其它运行时角色权限。 +- 验证:Docker PostgreSQL 回归先在修复前稳定得到空结果,新增迁移后要求 `admin_runtime` 能读取 `true:100:published`;staging 还需验证迁移账本、角色可见性及真实 Agent 接口不再返回 runtime disabled。 +- 防复发:任何对启用 RLS 的表新增 runtime grant 时,必须同时测试对应运行时角色的真实可见行,而不能只断言 `has_table_privilege=true`;feature flag 种子测试必须以 Web 实际使用的 `admin_runtime` 读取。 +- 相关记录:BUG-151、BUG-166 +- 修复版本:待提交 diff --git a/frontend/supabase/migrations/20260811030000_feature_flags_admin_runtime_read_policy.sql b/frontend/supabase/migrations/20260811030000_feature_flags_admin_runtime_read_policy.sql new file mode 100644 index 00000000..0496acf9 --- /dev/null +++ b/frontend/supabase/migrations/20260811030000_feature_flags_admin_runtime_read_policy.sql @@ -0,0 +1,15 @@ +begin; + +do $$ +begin + if exists (select 1 from pg_roles where rolname = 'admin_runtime') then + grant select on table public.feature_flags to admin_runtime; + + drop policy if exists feature_flags_admin_read on public.feature_flags; + create policy feature_flags_admin_read on public.feature_flags + for select to admin_runtime using (true); + end if; +end; +$$; + +commit; diff --git a/frontend/tests/rectification-v9-database.test.ts b/frontend/tests/rectification-v9-database.test.ts index 834610b3..f831f0a9 100644 --- a/frontend/tests/rectification-v9-database.test.ts +++ b/frontend/tests/rectification-v9-database.test.ts @@ -798,6 +798,7 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent" }); assert.equal(migration.status, 0, migration.stderr); assert.match(migration.stdout, /applied 20260813010000_agentic_rectification_v9_agent_api\.sql/); + assert.match(migration.stdout, /applied 20260811030000_feature_flags_admin_runtime_read_policy\.sql/); // The runtime selector flag is published and enabled. assert.equal( @@ -805,6 +806,16 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent" from public.feature_flags where flag_key = 'rectification_runtime_version'`), "true:100:published", ); + assert.equal( + fixture.psqlAs( + "admin_runtime", + "admin-runtime-test-password", + `select enabled || ':' || rollout_percentage || ':' || status + from public.feature_flags + where flag_key = 'rectification_runtime_version'`, + ), + "true:100:published", + ); // Run phases table exists with RLS. assert.equal(