fix: recover expired rectification handoffs
This commit is contained in:
@@ -329,3 +329,19 @@
|
||||
- 相关记录:BUG-009、BUG-017
|
||||
- 复发自:无
|
||||
- 修复版本:待提交
|
||||
|
||||
## BUG-019 | 原问题交接租约过期后永久显示处理中
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-07-22
|
||||
- 最近更新:2026-07-22
|
||||
- 影响面:生时校正范围终态、跨设备/失败重试、durable 原问题 handoff
|
||||
- 用户现象:一次继续咨询在输出前失败后,刷新仍无法重试,接口返回“原问题状态已经变化”。
|
||||
- 触发条件:handoff 处于 `claimed` 或 `executing`,两分钟租约已经过期,客户端重新加载交接。
|
||||
- 根因:数据库 projection 只看持久化 state,把过期租约仍投影为 `in_progress`;客户端因此不会发起 claim,而执行 RPC 又正确拒绝过期租约,形成永久卡死。
|
||||
- 修复:projection 将已过期的 `claimed`/`executing` 租约投影为 `pending`,复用既有加锁 claim RPC 原子接管;未过期租约仍保持 `in_progress`,双设备隔离不变。
|
||||
- 验证:迁移契约测试锁定过期租约恢复语义;生产制造过期 claim 后重新加载、claim、执行模型咨询和 settle。
|
||||
- 防复发:所有租约型状态的读取投影必须同时解释 lease deadline,不允许只依赖枚举 state。
|
||||
- 相关记录:BUG-017、BUG-018
|
||||
- 复发自:无
|
||||
- 修复版本:待提交
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
begin;
|
||||
|
||||
create or replace function public.conversational_rectification_handoff_projection(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid
|
||||
)
|
||||
returns jsonb
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
select pg_catalog.jsonb_build_object(
|
||||
'caseId', h.case_id,
|
||||
'turnVersion', c.turn_version,
|
||||
'question', h.question,
|
||||
'questionFingerprint', h.question_fingerprint,
|
||||
'requestId', h.request_id,
|
||||
'status', case
|
||||
when h.state = 'pending' then 'pending'
|
||||
when h.state in ('claimed', 'executing')
|
||||
and h.lease_expires_at <= pg_catalog.now() then 'pending'
|
||||
when h.state in ('claimed', 'executing') then 'in_progress'
|
||||
else 'consumed'
|
||||
end,
|
||||
'turn', public.conversational_rectification_case_projection(
|
||||
p_user_id, p_case_id
|
||||
) -> 'latest_turn'
|
||||
)
|
||||
from public.birth_time_rectification_question_handoffs h
|
||||
join public.birth_time_rectification_cases c
|
||||
on c.id = h.case_id and c.user_id = h.user_id
|
||||
where h.case_id = p_case_id and h.user_id = p_user_id;
|
||||
$$;
|
||||
|
||||
commit;
|
||||
@@ -201,3 +201,16 @@ test("migration keeps claim, billing recovery, settlement and ACL inside locked
|
||||
assert.match(sql, new RegExp(`grant execute on function public\\.${functionName}\\([\\s\\S]*?to service_role`, "i"));
|
||||
}
|
||||
});
|
||||
|
||||
test("expired handoff leases project as pending so another device can reclaim them", () => {
|
||||
const sql = readFileSync(new URL(
|
||||
"../supabase/migrations/20260722120000_recover_expired_rectification_handoff.sql",
|
||||
import.meta.url,
|
||||
), "utf8");
|
||||
|
||||
assert.match(
|
||||
sql,
|
||||
/h\.state in \('claimed', 'executing'\)[\s\S]*h\.lease_expires_at <= pg_catalog\.now\(\)[\s\S]*then 'pending'/i,
|
||||
);
|
||||
assert.match(sql, /create or replace function public\.conversational_rectification_handoff_projection/i);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user