Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b276bc8a8f | |||
| 752c8f4239 | |||
| d19d398221 |
@@ -2884,3 +2884,33 @@
|
||||
- 防复发:账号重置只允许使用普通管理员 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
|
||||
- 修复版本:待提交
|
||||
|
||||
## BUG-169 | V9 新建生时校正 Session 未绑定模型导致 Agent 立即返回模型不可用
|
||||
|
||||
- 状态:resolved(本地候选)
|
||||
- 首次发现:2026-08-11
|
||||
- 最近更新:2026-08-11
|
||||
- 影响面:V9 `open_agentic_rectification_case` 新建会话、既有 `model_id is null` 的生时校正会话,以及 `POST /api/rectification/agent` 的模型解析。
|
||||
- 用户现象:运行时开关恢复后,原请求继续返回 `409 模型暂不可用`;请求体包含有效 `modelId`,管理端模型及供应商也均为 published/enabled。
|
||||
- 触发条件:V9 Open Case RPC 原子创建 `birth_time_rectification` Session 后立即发送 opening。
|
||||
- 根因:RPC 插入 `chat_sessions` 时没有写入 `model_id`;前端仅在内存中把目录默认模型显示为当前选择,而 Agent 路由按安全合同只解析服务端持久化的 `chatSession.model_id/model_config_version`,不会信任请求体覆盖会话模型。
|
||||
- 修复:新增向前迁移 `20260811040000_rectification_session_default_model.sql`;数据库触发器为缺少模型的生时校正 Session 绑定当前 published/enabled 默认模型,由既有 pin trigger 固定配置版本,并一次性回填同类历史 Session。普通咨询 Session 与已有明确模型选择均不改变。
|
||||
- 验证:V9 PostgreSQL fixture 在创建 Case 前播种默认模型,要求 RPC 新建 Session 后持久化为 `v9-default-model:1`;staging 还需核对迁移账本、原 Session 回填结果和真实 opening 请求。
|
||||
- 防复发:任何服务器端创建 `birth_time_rectification` Session 的路径都必须在同一事务内得到可解析的持久化模型与版本;前端显示的默认模型不能替代数据库绑定。
|
||||
- 相关记录:BUG-060、BUG-163、BUG-168
|
||||
- 修复版本:待提交
|
||||
|
||||
+15
@@ -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;
|
||||
@@ -0,0 +1,49 @@
|
||||
begin;
|
||||
|
||||
create or replace function public.default_rectification_chat_session_model()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
if new.session_type = 'birth_time_rectification' and new.model_id is null then
|
||||
select c.model_id into new.model_id
|
||||
from public.model_config_versions v
|
||||
join public.model_configs c on c.id = v.config_id
|
||||
join public.model_providers p on p.id = v.provider_id
|
||||
where v.status = 'published'
|
||||
and v.enabled
|
||||
and v.is_default
|
||||
and p.enabled
|
||||
limit 1;
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
revoke all on function public.default_rectification_chat_session_model() from public, anon, authenticated;
|
||||
|
||||
drop trigger if exists chat_sessions_default_rectification_model on public.chat_sessions;
|
||||
create trigger chat_sessions_default_rectification_model
|
||||
before insert or update of session_type, model_id on public.chat_sessions
|
||||
for each row execute function public.default_rectification_chat_session_model();
|
||||
|
||||
with default_model as (
|
||||
select c.model_id
|
||||
from public.model_config_versions v
|
||||
join public.model_configs c on c.id = v.config_id
|
||||
join public.model_providers p on p.id = v.provider_id
|
||||
where v.status = 'published'
|
||||
and v.enabled
|
||||
and v.is_default
|
||||
and p.enabled
|
||||
limit 1
|
||||
)
|
||||
update public.chat_sessions s
|
||||
set model_id = default_model.model_id
|
||||
from default_model
|
||||
where s.session_type = 'birth_time_rectification'
|
||||
and s.model_id is null;
|
||||
|
||||
commit;
|
||||
@@ -101,6 +101,23 @@ test("v9 open is atomic, idempotent and resumes instead of duplicating", { skip:
|
||||
where id = '${userId}';
|
||||
`);
|
||||
|
||||
fixture.psql(`
|
||||
with provider as (
|
||||
insert into public.model_providers (code, name, provider_type, encrypted_api_key, enabled)
|
||||
values ('v9-test', 'V9 Test', 'openai', 'test-ciphertext', true)
|
||||
returning id
|
||||
), config as (
|
||||
insert into public.model_configs (model_id)
|
||||
values ('v9-default-model')
|
||||
returning id
|
||||
)
|
||||
insert into public.model_config_versions (
|
||||
config_id, version, provider_id, label, provider_model, enabled, is_default, status, published_at
|
||||
)
|
||||
select config.id, 1, provider.id, 'V9 Default', 'gpt-test', true, true, 'published', now()
|
||||
from config cross join provider;
|
||||
`);
|
||||
|
||||
const service = createLocalPostgresDataClient(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
null,
|
||||
@@ -134,6 +151,10 @@ test("v9 open is atomic, idempotent and resumes instead of duplicating", { skip:
|
||||
fixture.psql(`select count(*) from public.chat_sessions where user_id = '${userId}'`),
|
||||
"1",
|
||||
);
|
||||
assert.equal(
|
||||
fixture.psql(`select model_id || ':' || model_config_version from public.chat_sessions where id = '${sessionId}'`),
|
||||
"v9-default-model:1",
|
||||
);
|
||||
assert.equal(
|
||||
fixture.psql(
|
||||
`select count(*) from public.agentic_rectification_open_ledger where user_id = '${userId}'`,
|
||||
@@ -798,6 +819,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 +827,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(
|
||||
|
||||
Reference in New Issue
Block a user